aboutsummaryrefslogtreecommitdiffstats
path: root/testing/src/test/java/org
diff options
context:
space:
mode:
authorAndy Clement <aclement@pivotal.io>2019-01-30 16:15:14 -0800
committerAndy Clement <aclement@pivotal.io>2019-01-30 16:15:14 -0800
commitf4d5e9485ae910220e06b0b6c778587ed6b4ebb3 (patch)
tree077bc311c2f2532a81483ad8c05fa898c6b40269 /testing/src/test/java/org
parentcda73a6a5864b76e252ed0d3b69ff4b55a6def4b (diff)
downloadaspectj-f4d5e9485ae910220e06b0b6c778587ed6b4ebb3.tar.gz
aspectj-f4d5e9485ae910220e06b0b6c778587ed6b4ebb3.zip
mavenizing testing - done
Diffstat (limited to 'testing/src/test/java/org')
-rw-r--r--testing/src/test/java/org/aspectj/internal/tools/ant/taskdefs/Ajctest.java1900
-rw-r--r--testing/src/test/java/org/aspectj/internal/tools/ant/taskdefs/MainWrapper.java181
-rw-r--r--testing/src/test/java/org/aspectj/testing/AjcTest.java169
-rw-r--r--testing/src/test/java/org/aspectj/testing/AntSpec.java265
-rw-r--r--testing/src/test/java/org/aspectj/testing/AutowiredXMLBasedAjcTestCase.java154
-rw-r--r--testing/src/test/java/org/aspectj/testing/CompileSpec.java336
-rw-r--r--testing/src/test/java/org/aspectj/testing/ExpectedMessageSpec.java94
-rw-r--r--testing/src/test/java/org/aspectj/testing/FileSpec.java93
-rw-r--r--testing/src/test/java/org/aspectj/testing/ITestStep.java28
-rw-r--r--testing/src/test/java/org/aspectj/testing/MakeTestClass.java118
-rw-r--r--testing/src/test/java/org/aspectj/testing/OutputLine.java42
-rw-r--r--testing/src/test/java/org/aspectj/testing/OutputSpec.java134
-rw-r--r--testing/src/test/java/org/aspectj/testing/RunSpec.java262
-rw-r--r--testing/src/test/java/org/aspectj/testing/TestingModuleTests.java36
-rw-r--r--testing/src/test/java/org/aspectj/testing/WeaveSpec.java164
-rw-r--r--testing/src/test/java/org/aspectj/testing/XMLBasedAjcTestCase.java522
-rw-r--r--testing/src/test/java/org/aspectj/testing/XMLBasedAjcTestCaseForJava10OrLater.java31
-rw-r--r--testing/src/test/java/org/aspectj/testing/XMLBasedAjcTestCaseForJava11OrLater.java31
-rw-r--r--testing/src/test/java/org/aspectj/testing/XMLBasedAjcTestCaseForJava9OrLater.java32
-rw-r--r--testing/src/test/java/org/aspectj/testing/ajde/CompileCommand.java439
-rw-r--r--testing/src/test/java/org/aspectj/testing/harness/bridge/AbstractRunSpec.java918
-rw-r--r--testing/src/test/java/org/aspectj/testing/harness/bridge/AjcMessageHandler.java343
-rw-r--r--testing/src/test/java/org/aspectj/testing/harness/bridge/AjcTest.java586
-rw-r--r--testing/src/test/java/org/aspectj/testing/harness/bridge/CompilerRun.java1840
-rw-r--r--testing/src/test/java/org/aspectj/testing/harness/bridge/DirChanges.java614
-rw-r--r--testing/src/test/java/org/aspectj/testing/harness/bridge/FlatSuiteReader.java376
-rw-r--r--testing/src/test/java/org/aspectj/testing/harness/bridge/Globals.java162
-rw-r--r--testing/src/test/java/org/aspectj/testing/harness/bridge/IAjcRun.java44
-rw-r--r--testing/src/test/java/org/aspectj/testing/harness/bridge/IRunSpec.java24
-rw-r--r--testing/src/test/java/org/aspectj/testing/harness/bridge/IncCompilerRun.java503
-rw-r--r--testing/src/test/java/org/aspectj/testing/harness/bridge/JavaRun.java899
-rw-r--r--testing/src/test/java/org/aspectj/testing/harness/bridge/RunSpecIterator.java256
-rw-r--r--testing/src/test/java/org/aspectj/testing/harness/bridge/Sandbox.java575
-rw-r--r--testing/src/test/java/org/aspectj/testing/harness/bridge/TestingBridgeTests.java38
-rw-r--r--testing/src/test/java/org/aspectj/testing/harness/bridge/Validator.java564
-rw-r--r--testing/src/test/java/org/aspectj/testing/run/IRun.java62
-rw-r--r--testing/src/test/java/org/aspectj/testing/run/IRunIterator.java62
-rw-r--r--testing/src/test/java/org/aspectj/testing/run/IRunListener.java38
-rw-r--r--testing/src/test/java/org/aspectj/testing/run/IRunStatus.java195
-rw-r--r--testing/src/test/java/org/aspectj/testing/run/IRunValidator.java28
-rw-r--r--testing/src/test/java/org/aspectj/testing/run/RunIterator.java136
-rw-r--r--testing/src/test/java/org/aspectj/testing/run/RunListener.java116
-rw-r--r--testing/src/test/java/org/aspectj/testing/run/RunListeners.java89
-rw-r--r--testing/src/test/java/org/aspectj/testing/run/RunStatus.java442
-rw-r--r--testing/src/test/java/org/aspectj/testing/run/RunValidator.java210
-rw-r--r--testing/src/test/java/org/aspectj/testing/run/Runner.java510
-rw-r--r--testing/src/test/java/org/aspectj/testing/run/WrappedRunIterator.java72
-rw-r--r--testing/src/test/java/org/aspectj/testing/taskdefs/AjcTaskCompileCommand.java294
-rw-r--r--testing/src/test/java/org/aspectj/testing/taskdefs/TaskdefTests.java29
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/AccumulatingFileFilter.java53
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/BridgeUtil.java517
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/CollectorFileFilter.java88
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/Diffs.java547
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/FileUtil.java745
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/IntRange.java118
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/IntValidator.java21
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/IteratorWrapper.java147
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/LangUtil.java1271
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/LineReader.java204
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/LinkCheck.java546
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/Node.java162
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/NullPrintStream.java95
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/ObjectChecker.java51
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/ProxyPrintStream.java104
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/RunUtils.java368
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/SFileReader.java184
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/StandardObjectChecker.java127
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/StreamSniffer.java76
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/StreamsHandler.java217
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/StringAccumulator.java99
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/StringVisitor.java28
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/StructureModelUtil.java263
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/TestClassLoader.java157
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/TestDiffs.java360
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/UtilLineReader.java212
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/UtilTests.java39
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/ValidFileFilter.java123
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/WebInstall.java208
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/options/Option.java707
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/options/Options.java152
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/options/OptionsTests.java30
-rw-r--r--testing/src/test/java/org/aspectj/testing/util/options/Values.java714
-rw-r--r--testing/src/test/java/org/aspectj/testing/xml/AjcSpecXmlReader.java526
-rw-r--r--testing/src/test/java/org/aspectj/testing/xml/IXmlWritable.java (renamed from testing/src/test/java/org/aspectj/testing/xml/TestingXmlTests.java)30
-rw-r--r--testing/src/test/java/org/aspectj/testing/xml/MessageListXmlReader.java215
-rw-r--r--testing/src/test/java/org/aspectj/testing/xml/SoftMessage.java368
-rw-r--r--testing/src/test/java/org/aspectj/testing/xml/SoftSourceLocation.java133
-rw-r--r--testing/src/test/java/org/aspectj/testing/xml/XMLWriter.java365
88 files changed, 25234 insertions, 192 deletions
diff --git a/testing/src/test/java/org/aspectj/internal/tools/ant/taskdefs/Ajctest.java b/testing/src/test/java/org/aspectj/internal/tools/ant/taskdefs/Ajctest.java
new file mode 100644
index 000000000..8fb274176
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/internal/tools/ant/taskdefs/Ajctest.java
@@ -0,0 +1,1900 @@
+/* *******************************************************************
+ * 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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.internal.tools.ant.taskdefs;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.LayoutManager;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.DateFormat;
+//import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTable;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.ListSelectionModel;
+import javax.swing.border.BevelBorder;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.table.DefaultTableModel;
+import javax.swing.table.TableModel;
+import javax.swing.text.StyleContext;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Location;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Target;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.taskdefs.Delete;
+//import org.apache.tools.ant.taskdefs.ExecTask;
+import org.apache.tools.ant.taskdefs.Java;
+import org.apache.tools.ant.taskdefs.LogOutputStream;
+import org.apache.tools.ant.taskdefs.Mkdir;
+import org.apache.tools.ant.taskdefs.StreamPumper;
+import org.apache.tools.ant.types.Commandline;
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.Path;
+import org.apache.tools.ant.types.Reference;
+import org.aspectj.util.LangUtil;
+
+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 Stats ajdocStats = new Stats();
+ private Stats ajcStats = new Stats();
+ private Stats runStats = new Stats();
+// private 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<Argument> args = new Vector<>();
+ private List<Testset> testsets = new Vector<>();
+ private Path classpath;
+ private Path internalclasspath;
+ private File destdir;
+ private File dir;
+ private File errorfile;
+ private List<Run> testclasses = new Vector<>();
+ private boolean nocompile;
+ private Ajdoc ajdoc = null;
+ private boolean noclean;
+ private boolean noverify;
+ private List<String> 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 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<String> 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<Argfile> argfileNames = new Vector<>();
+ public List<File> argfiles;
+ public List<File> files;
+ public List<Argument> args = new Vector<>();
+ public String classname;
+ private boolean havecludes = false;
+ private List<Run> testclasses = new Vector<>();
+ private Path classpath;
+ private Path internalclasspath;
+ private Ajdoc ajdoc = null;
+ private boolean fork = false;
+ private boolean noclean;
+ private List<String> depends = new Vector<>();
+ public String toString() {
+ String str = "";
+ if (files.size() > 0) {
+ str += "files:" + "\n";
+ for (Iterator<File> i = files.iterator(); i.hasNext();) {
+ str += "\t" + i.next() + "\n";
+ }
+ }
+ if (argfiles.size() > 0) {
+ str += "argfiles:" + "\n";
+ for (Iterator<File> 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<Argfile> iter = argfileNames.iterator(); iter.hasNext();) {
+ String name = 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<Run> i = this.testclasses.iterator(); i.hasNext();) {
+ 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<Arg> args) throws BuildException {
+ if (testset.files.size() > 0) {
+ log("\tfiles:");
+ for (Iterator<File> i = testset.files.iterator(); i.hasNext();) {
+ log("\t " + i.next());
+ }
+ }
+ if (testset.argfiles.size() > 0) {
+ log("\targfiles:");
+ for (Iterator<File> i = testset.argfiles.iterator(); i.hasNext();) {
+ log("\t " + i.next());
+ }
+ }
+ if (args.size() > 0) {
+ log("\targs:");
+ for (Iterator<Arg> i = args.iterator(); i.hasNext();) {
+ log("\t " + i.next());
+ }
+ }
+ if (testset.testclasses.size() > 0) {
+ log("\tclasses:");
+ for (Iterator<Run> 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<String> 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<Run> i = testset.testclasses.iterator(); i.hasNext();) {
+ Run testclass = 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<Testset,List<List<Arg>>> testsetToArgcombo = new HashMap<>();
+ List<Integer> argcombos = new Vector<>();
+ for (Testset testset: testsets) {
+ testset.resolve();
+ List<Argument> bothargs = new Vector<>(args);
+ bothargs.addAll(testset.args);
+ List<List<Arg>> argcombo = argcombo(bothargs);
+ argcombos.add(new Integer(argcombo.size()));
+ testsetToArgcombo.put(testset, argcombo);
+ }
+ while (!testsetToArgcombo.isEmpty()) {
+ int testsetCounter = 1;
+ for (Iterator<Testset> iter = testsets.iterator(); iter.hasNext(); testsetCounter++) {
+ Testset testset = iter.next();
+ List<List<Arg>> argcombo = testsetToArgcombo.get(testset);
+ if (argcombo.size() == 0) {
+ testsetToArgcombo.remove(testset);
+ continue;
+ }
+ List<Arg> args = argcombo.remove(0);
+ final String startStr = "Testset " + testsetCounter + " of " + testsets.size();
+ String str = startStr + " / Combo " + testsetCounter + " 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<Failure> 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 = Ajctest.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", current.ajdocStats.goods+"", current.ajdocStats.fails+"",
+ "ajc", current.ajcStats.goods +"", current.ajcStats.fails +"",
+ "run", current.runStats.goods +"", current.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() +
+ current.ajdocStats.fails +
+ current.ajcStats.fails +
+ current.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<List<Arg>> argcombo(List<Argument> arguments) {
+ List<Argument> combos = new Vector<>();
+ List<Arg> always = new Vector<>();
+ for (Iterator<Argument> iter = arguments.iterator(); iter.hasNext();) {
+ Argument arg = 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<List<Arg>> argcombo = combinations(combos);
+ for (Iterator<Arg> iter = always.iterator(); iter.hasNext();) {
+ Arg arg = iter.next();
+ for (Iterator<List<Arg>> comboiter = argcombo.iterator(); comboiter.hasNext();) {
+ 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<List<Arg>> combinations(List<Argument> arglist) {
+ List<List<Arg>> result = new Vector<>();
+ result.add(new Vector<Arg>());
+ for (Iterator<Argument> iter = arglist.iterator(); iter.hasNext();) {
+ Argument arg = iter.next();
+ int N = result.size();
+ for (int i = 0; i < N; i++) {
+ List<Arg> to = result.remove(0);
+ for (Iterator<String> valiter = arg.values.iterator(); valiter.hasNext();) {
+ List<Arg> 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/test/java/org/aspectj/internal/tools/ant/taskdefs/MainWrapper.java b/testing/src/test/java/org/aspectj/internal/tools/ant/taskdefs/MainWrapper.java
new file mode 100644
index 000000000..6ce22df24
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+
+package org.aspectj.internal.tools.ant.taskdefs;
+
+import java.io.File;
+import java.io.PrintStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import org.aspectj.testing.util.LangUtil;
+
+/**
+ * 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.
+ */
+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/test/java/org/aspectj/testing/AjcTest.java b/testing/src/test/java/org/aspectj/testing/AjcTest.java
new file mode 100644
index 000000000..4cb9a8722
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/AjcTest.java
@@ -0,0 +1,169 @@
+/* *******************************************************************
+ * Copyright (c) 2004,2018 IBM Corporation, contributors
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * ******************************************************************/
+package org.aspectj.testing;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.aspectj.tools.ajc.AjcTestCase;
+import org.aspectj.util.LangUtil;
+
+/**
+ * @author Adrian Colyer
+ * @author Andy Clement
+ */
+public class AjcTest {
+
+// private static boolean is13VMOrGreater = true;
+ private static boolean is14VMOrGreater = true;
+ private static boolean is15VMOrGreater = false;
+ private static boolean is16VMOrGreater = false;
+ private static boolean is17VMOrGreater = false;
+ private static boolean is18VMOrGreater = false;
+ private static boolean is19VMOrGreater = false;
+ private static boolean is10VMOrGreater = false;
+ private static boolean is11VMOrGreater = false;
+
+ static { // matching logic is also in org.aspectj.util.LangUtil
+ is14VMOrGreater = LangUtil.is14VMOrGreater();
+ is15VMOrGreater = LangUtil.is15VMOrGreater();
+ is16VMOrGreater = LangUtil.is16VMOrGreater();
+ is17VMOrGreater = LangUtil.is17VMOrGreater();
+ is18VMOrGreater = LangUtil.is18VMOrGreater();
+ is19VMOrGreater = LangUtil.is19VMOrGreater();
+ is10VMOrGreater = LangUtil.is10VMOrGreater();
+ is11VMOrGreater = LangUtil.is11VMOrGreater();
+ }
+
+ private List<ITestStep> testSteps = new ArrayList<ITestStep>();
+
+ private String dir;
+ private String pr;
+ private String title;
+ private String keywords;
+ private String comment;
+ private String vmLevel = "1.3";
+
+ public AjcTest() {
+ }
+
+ public void addTestStep(ITestStep step) {
+ testSteps.add(step);
+ step.setTest(this);
+ }
+
+ public boolean runTest(AjcTestCase testCase) {
+ if (!canRunOnThisVM()) return false;
+ try {
+ System.out.print("TEST: " + getTitle() + "\t");
+ for (ITestStep step: testSteps) {
+ step.setBaseDir(getDir());
+ System.out.print(".");
+ step.execute(testCase);
+ }
+ } finally {
+ System.out.println("DONE");
+ }
+ return true;
+ }
+
+ public boolean canRunOnThisVM() {
+ if (vmLevel.equals("1.3")) return true;
+ boolean canRun = true;
+ if (vmLevel.equals("1.4")) canRun = is14VMOrGreater;
+ if (vmLevel.equals("1.5")) canRun = is15VMOrGreater;
+ if (vmLevel.equals("1.6")) canRun = is16VMOrGreater;
+ if (vmLevel.equals("1.7")) canRun = is17VMOrGreater;
+ if (vmLevel.equals("1.8")) canRun = is18VMOrGreater;
+ if (vmLevel.equals("1.9")) canRun = is19VMOrGreater;
+ if (vmLevel.equals("10")) canRun = is10VMOrGreater;
+ if (vmLevel.equals("11")) canRun = is11VMOrGreater;
+ if (!canRun) {
+ System.out.println("***SKIPPING TEST***" + getTitle()+ " needs " + getVmLevel()
+ + ", currently running on " + System.getProperty("java.vm.version"));
+ }
+ return canRun;
+ }
+
+ /**
+ * @return Returns the comment.
+ */
+ public String getComment() {
+ return comment;
+ }
+ /**
+ * @param comment The comment to set.
+ */
+ public void setComment(String comment) {
+ this.comment = comment;
+ }
+ /**
+ * @return Returns the dir.
+ */
+ public String getDir() {
+ return dir;
+ }
+ /**
+ * @param dir The dir to set.
+ */
+ public void setDir(String dir) {
+ dir = "../tests/" + dir;
+ this.dir = dir;
+ }
+ /**
+ * @return Returns the keywords.
+ */
+ public String getKeywords() {
+ return keywords;
+ }
+ /**
+ * @param keywords The keywords to set.
+ */
+ public void setKeywords(String keywords) {
+ this.keywords = keywords;
+ }
+ /**
+ * @return Returns the pr.
+ */
+ public String getPr() {
+ return pr;
+ }
+ /**
+ * @param pr The pr to set.
+ */
+ public void setPr(String pr) {
+ this.pr = pr;
+ }
+ /**
+ * @return Returns the title.
+ */
+ public String getTitle() {
+ return title;
+ }
+ /**
+ * @param title The title to set.
+ */
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ /**
+ * @param vmLevel The vmLevel to set.
+ */
+ public void setVm(String vmLevel) {
+ this.vmLevel = vmLevel;
+ }
+
+ /**
+ * @return Returns the vmLevel.
+ */
+ public String getVmLevel() {
+ return vmLevel;
+ }
+}
diff --git a/testing/src/test/java/org/aspectj/testing/AntSpec.java b/testing/src/test/java/org/aspectj/testing/AntSpec.java
new file mode 100644
index 000000000..ceba08b29
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/AntSpec.java
@@ -0,0 +1,265 @@
+/*******************************************************************************
+ * Copyright (c) 2005 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Alexandre Vasseur initial implementation
+ *******************************************************************************/
+package org.aspectj.testing;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.util.StringTokenizer;
+
+import org.apache.tools.ant.BuildEvent;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DefaultLogger;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.ProjectHelper;
+import org.apache.tools.ant.Target;
+import org.apache.tools.ant.taskdefs.Java;
+import org.apache.tools.ant.types.Path;
+import org.aspectj.tools.ajc.AjcTestCase;
+
+/**
+ * Element that allow to run an abritrary Ant target in a sandbox.
+ * <p/>
+ * Such a spec is used in a "<ajc-test><ant file="myAnt.xml" [target="..."] [verbose="true"]/> XML element. The "target" is
+ * optional. If not set, default myAnt.xml target is used. The "file" file is looked up from the <ajc-test dir="..."/> attribute. If
+ * "verbose" is set to "true", the ant -v output is piped, else nothing is reported except errors.
+ * <p/>
+ * The called Ant target benefits from 2 implicit variables: "${aj.sandbox}" points to the test current sandbox folder. "aj.path" is
+ * an Ant refid on the classpath formed with the sandbox folder + ltw + the AjcTestCase classpath (ie usually aspectjrt, junit, and
+ * testing infra)
+ * <p/>
+ * Element "<stdout><line text="..">" and "<stderr><line text="..">" can be used. For now a full match is performed on the output of
+ * the runned target only (not the whole Ant invocation). This is experimental and advised to use a "<junit>" task instead or a
+ * "<java>" whose main that throws some exception upon failure.
+ *
+ *
+ * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
+ */
+public class AntSpec implements ITestStep {
+
+ // ALSO SEE AJC
+ private final static String DEFAULT_LTW_CLASSPATH_ENTRIES = ".." + File.separator + "asm/bin" + File.pathSeparator + ".."
+ + File.separator + "bridge/bin" + File.pathSeparator + ".." + File.separator + "loadtime/bin" + File.pathSeparator
+ + ".." + File.separator + "loadtime5/bin" + File.pathSeparator + ".." + File.separator + "weaver/bin"
+ + File.pathSeparator + ".." + File.separator + "org.aspectj.matcher/bin" + File.pathSeparator + ".." + File.separator
+ + "lib/bcel/bcel.jar" + File.pathSeparator + ".." + File.separator + "lib/bcel/bcel-verifier.jar";;
+
+ private boolean m_verbose = false;
+ private AjcTest m_ajcTest;
+ private OutputSpec m_stdErrSpec;
+ private OutputSpec m_stdOutSpec;
+ private String m_antFile;
+ private String m_antTarget;
+
+ public void execute(final AjcTestCase inTestCase) {
+ final String failMessage = "test \"" + m_ajcTest.getTitle() + "\" failed: ";
+
+ File buildFile = new File(m_ajcTest.getDir() + File.separatorChar + m_antFile);
+ if (!buildFile.exists()) {
+ AjcTestCase.fail(failMessage + "no such Ant file " + buildFile.getAbsolutePath());
+ }
+ Project p = new Project();
+ final StringBuffer stdout = new StringBuffer();
+ final StringBuffer stderr = new StringBuffer();
+ final StringBuffer verboseLog = new StringBuffer();
+ try {
+ // read the Ant file
+ p.init();
+ p.setUserProperty("ant.file", buildFile.getAbsolutePath());
+ // setup aj.sandbox
+ p.setUserProperty("aj.sandbox", inTestCase.getSandboxDirectory().getAbsolutePath());
+ // setup aj.dir "modules" folder
+ p.setUserProperty("aj.root", new File("..").getAbsolutePath());
+
+ // create the test implicit path aj.path that contains the sandbox + regular test infra path
+ Path path = new Path(p, inTestCase.getSandboxDirectory().getAbsolutePath());
+ populatePath(path, DEFAULT_LTW_CLASSPATH_ENTRIES);
+ populatePath(path, AjcTestCase.DEFAULT_CLASSPATH_ENTRIES);
+ p.addReference("aj.path", path);
+ p.setBasedir(buildFile.getAbsoluteFile().getParent());
+ ProjectHelper helper = ProjectHelper.getProjectHelper();
+ helper.parse(p, buildFile);
+
+ // use default target if no target specified
+ if (m_antTarget == null) {
+ m_antTarget = p.getDefaultTarget();
+ }
+
+ // make sure we listen for failure
+ DefaultLogger consoleLogger = new DefaultLogger() {
+ public void buildFinished(BuildEvent event) {
+ super.buildFinished(event);
+ if (event.getException() != null) {
+
+ try {
+ File antout = new File(inTestCase.getSandboxDirectory().getAbsolutePath(), "antout");
+ if (antout.exists()) {
+ stdout.append("Forked java command stdout:\n");
+ System.out.println("Forked java command stdout:");
+ FileReader fr = new FileReader(antout);
+ BufferedReader br = new BufferedReader(fr);
+ String line = br.readLine();
+ while (line != null) {
+ stdout.append(line).append("\n");
+ System.out.println(stdout);
+ line = br.readLine();
+ }
+ fr.close();
+ }
+
+ File anterr = new File(inTestCase.getSandboxDirectory().getAbsolutePath(), "anterr");
+ if (anterr.exists()) {
+ stdout.append("Forked java command stderr:\n");
+ System.out.println("Forked java command stderr:");
+ FileReader fr = new FileReader(anterr);
+ BufferedReader br = new BufferedReader(fr);
+ String line = br.readLine();
+ while (line != null) {
+ stdout.append(line).append("\n");
+ System.out.println(stdout);
+ line = br.readLine();
+ }
+ fr.close();
+ }
+ } catch (Exception e) {
+ System.out.println("Exception whilst loading forked java task output " + e.getMessage() + "\n");
+ e.printStackTrace();
+ stdout.append("Exception whilst loading forked java task output " + e.getMessage() + "\n");
+ }
+
+ StringBuffer message = new StringBuffer();
+ message.append(event.getException().toString()).append("\n");
+ message.append(verboseLog);
+ message.append(stdout);
+ message.append(stderr);
+ // AjcTestCase.fail(failMessage + "failure " + event.getException());
+ AjcTestCase.fail(message.toString());
+ }
+ }
+
+ public void targetFinished(BuildEvent event) {
+ super.targetFinished(event);
+ if (event.getException() != null) {
+ AjcTestCase.fail(failMessage + "failure in '" + event.getTarget() + "' " + event.getException());
+ }
+ }
+
+ public void messageLogged(BuildEvent event) {
+ super.messageLogged(event);
+
+ Target target = event.getTarget();
+ if (target != null && m_antTarget.equals(target.getName()) && event.getSource() instanceof Java)
+ switch (event.getPriority()) {
+ case Project.MSG_INFO:
+ stdout.append(event.getMessage()).append('\n');
+ break;
+ case Project.MSG_WARN:
+ stderr.append(event.getMessage()).append('\n');
+ break;
+ case Project.MSG_VERBOSE:
+ verboseLog.append(event.getMessage()).append('\n');
+ break;
+ }
+ }
+ };
+ consoleLogger.setErrorPrintStream(System.err);
+ consoleLogger.setOutputPrintStream(System.out);
+ consoleLogger.setMessageOutputLevel(m_verbose ? Project.MSG_VERBOSE : Project.MSG_ERR);
+ p.addBuildListener(consoleLogger);
+ } catch (Throwable t) {
+ AjcTestCase.fail(failMessage + "invalid Ant script :" + t.toString());
+ }
+ try {
+ p.setProperty("verbose", "true");
+ p.fireBuildStarted();
+ p.executeTarget(m_antTarget);
+ p.fireBuildFinished(null);
+ } catch (BuildException e) {
+ p.fireBuildFinished(e);
+ } catch (Throwable t) {
+ AjcTestCase.fail(failMessage + "error when invoking target :" + t.toString());
+ }
+
+ /* See if stdout/stderr matches test specification */
+ if (m_stdOutSpec != null) {
+ m_stdOutSpec.matchAgainst(stdout.toString());
+ }
+ if (m_stdErrSpec != null) {
+ String stderr2 = stderr.toString();
+ // Working around this rediculous message that still comes out of Java7 builds:
+ if (stderr2.indexOf("Class JavaLaunchHelper is implemented in both")!=-1 && stderr2.indexOf('\n')!=-1) {
+ stderr2 = stderr2.replaceAll("objc\\[[0-9]*\\]: Class JavaLaunchHelper is implemented in both [^\n]*\n","");
+ }
+ // JDK 11 is complaining about illegal reflective calls - temporary measure ignore these - does that get all tests passing and this is the last problem?
+ if (stderr2.indexOf("WARNING: Illegal reflective access using Lookup on org.aspectj.weaver.loadtime.ClassLoaderWeavingAdaptor") != -1) {
+// WARNING: An illegal reflective access operation has occurred
+// WARNING: Illegal reflective access using Lookup on org.aspectj.weaver.loadtime.ClassLoaderWeavingAdaptor (file:/Users/aclement/gits/org.aspectj/loadtime/bin/) to class java.lang.ClassLoader
+// WARNING: Please consider reporting this to the maintainers of org.aspectj.weaver.loadtime.ClassLoaderWeavingAdaptor
+// WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
+// WARNING: All illegal access operations will be denied in a future release
+
+ stderr2 = stderr2.replaceAll("WARNING: An illegal reflective access operation has occurred\n","");
+ stderr2 = stderr2.replaceAll("WARNING: Illegal reflective access using Lookup on org.aspectj.weaver.loadtime.ClassLoaderWeavingAdaptor[^\n]*\n","");
+ stderr2 = stderr2.replaceAll("WARNING: Please consider reporting this to the maintainers of org.aspectj.weaver.loadtime.ClassLoaderWeavingAdaptor\n","");
+ stderr2 = stderr2.replaceAll("WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations\n","");
+ stderr2 = stderr2.replaceAll("WARNING: All illegal access operations will be denied in a future release\n","");
+ }
+
+ m_stdErrSpec.matchAgainst(stderr2);
+ }
+ }
+
+ public void addStdErrSpec(OutputSpec spec) {
+ if (m_stdErrSpec != null)
+ throw new UnsupportedOperationException("only one 'stderr' allowed in 'ant'");
+ m_stdErrSpec = spec;
+ }
+
+ public void addStdOutSpec(OutputSpec spec) {
+ if (m_stdOutSpec != null)
+ throw new UnsupportedOperationException("only one 'stdout' allowed in 'ant'");
+ m_stdOutSpec = spec;
+ }
+
+ public void setVerbose(String verbose) {
+ if (verbose != null && "true".equalsIgnoreCase(verbose)) {
+ m_verbose = true;
+ }
+ }
+
+ public void setFile(String file) {
+ m_antFile = file;
+ }
+
+ public void setTarget(String target) {
+ m_antTarget = target;
+ }
+
+ public void addExpectedMessage(ExpectedMessageSpec message) {
+ throw new UnsupportedOperationException("don't use 'message' in 'ant' specs.");
+ }
+
+ public void setBaseDir(String dir) {
+ ;
+ }
+
+ public void setTest(AjcTest test) {
+ m_ajcTest = test;
+ }
+
+ private static void populatePath(Path path, String pathEntries) {
+ StringTokenizer st = new StringTokenizer(pathEntries, File.pathSeparator);
+ while (st.hasMoreTokens()) {
+ path.setPath(new File(st.nextToken()).getAbsolutePath());
+ }
+ }
+}
diff --git a/testing/src/test/java/org/aspectj/testing/AutowiredXMLBasedAjcTestCase.java b/testing/src/test/java/org/aspectj/testing/AutowiredXMLBasedAjcTestCase.java
new file mode 100644
index 000000000..467d9f236
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/AutowiredXMLBasedAjcTestCase.java
@@ -0,0 +1,154 @@
+/*******************************************************************************
+ * Copyright (c) 2005 Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * initial implementation Alexandre Vasseur
+ *******************************************************************************/
+package org.aspectj.testing;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import junit.extensions.TestSetup;
+
+import java.lang.reflect.Method;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.HashMap;
+import java.io.InputStreamReader;
+import java.io.FileInputStream;
+
+import org.apache.commons.digester.Digester;
+import org.aspectj.tools.ajc.Ajc;
+
+/**
+ * Autowiring of XML test spec file as JUnit tests.
+ * <p/>
+ * Extend this class and implement the getSpecFile and the static suite() method.
+ * All tests described in the XML spec file will be auto-registered as JUnit tests.
+ * Any regular test() method will be registered as well.
+ *
+ * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
+ */
+public abstract class AutowiredXMLBasedAjcTestCase extends XMLBasedAjcTestCase {
+
+ private Map<String,AjcTest> testMap = new HashMap<String,AjcTest>();
+
+ public void addTest(AjcTest test) {
+ testMap.put(test.getTitle(), test);
+ }
+
+ /*
+ * Return a map from (String) test title -> AjcTest
+ */
+ protected Map<String,AjcTest> getSuiteTests() {
+ return testMap;
+ }
+
+ public static Test loadSuite(Class<?> testCaseClass) {
+ TestSuite suite = new TestSuite(testCaseClass.getName());
+ //suite.addTestSuite(testCaseClass);
+
+ // wire the spec file
+ try {
+ final AutowiredXMLBasedAjcTestCase wired = (AutowiredXMLBasedAjcTestCase) testCaseClass.newInstance();
+ System.out.println("LOADING SUITE: " + wired.getSpecFile().getPath());
+ Digester d = wired.getDigester();
+ try {
+ InputStreamReader isr = new InputStreamReader(new FileInputStream(wired.getSpecFile()));
+ d.parse(isr);
+ } catch (Exception ex) {
+ fail("Unable to load suite " + wired.getSpecFile().getPath() + " : " + ex);
+ }
+ wired.ajc = new Ajc();
+
+ Map<String,AjcTest> ajTests = wired.getSuiteTests();
+
+ for (Iterator<Map.Entry<String,AjcTest>> iterator = ajTests.entrySet().iterator(); iterator.hasNext();) {
+ final Map.Entry<String,AjcTest> entry = iterator.next();
+
+ suite.addTest(
+ new TestCase(entry.getKey().toString()) {
+
+ protected void runTest() {
+ entry.getValue().runTest(wired);
+ }
+
+ public String getName() {
+ return entry.getKey();
+ }
+ }
+ );
+ }
+ } catch (Throwable t) {
+ final String message = t.toString();
+ suite.addTest(
+ new TestCase("error") {
+ protected void runTest() {
+ fail(message);
+ }
+ }
+ );
+ }
+
+ // wire the test methods as well if any
+ // this simple check avoids failure when no test.. method is found.
+ // it could be refined to lookup in the hierarchy as well, and excluding private method as JUnit does.
+ Method[] testMethods = testCaseClass.getDeclaredMethods();
+ for (int i = 0; i < testMethods.length; i++) {
+ Method testMethod = testMethods[i];
+ if (testMethod.getName().startsWith("test")) {
+ suite.addTestSuite(testCaseClass);
+ break;
+ }
+ }
+
+ TestSetup wrapper = new TestSetup(suite) {
+ /* (non-Javadoc)
+ * @see junit.extensions.TestSetup#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ //suiteLoaded = false;
+ }
+ /* (non-Javadoc)
+ * @see junit.extensions.TestSetup#tearDown()
+ */
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ //suiteLoaded = false;
+ }
+ };
+ return wrapper;
+
+ //return suite;
+ }
+
+ /* (non-Javadoc)
+ * @see org.aspectj.tools.ajc.AjcTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ /**
+ * This helper method runs the test with the given title in the
+ * suite spec file. All tests steps in given ajc-test execute
+ * in the same sandbox.
+ */
+ protected void runTest(String title) {
+ AjcTest currentTest = (AjcTest) testMap.get(title);
+ if (currentTest == null) {
+ fail("No test '" + title + "' in suite.");
+ }
+ ajc.setShouldEmptySandbox(true);
+ currentTest.runTest(this);
+ }
+
+
+
+}
diff --git a/testing/src/test/java/org/aspectj/testing/CompileSpec.java b/testing/src/test/java/org/aspectj/testing/CompileSpec.java
new file mode 100644
index 000000000..22570e3d1
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/CompileSpec.java
@@ -0,0 +1,336 @@
+/* *******************************************************************
+ * Copyright (c) 2004,2016 IBM Corporation
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Adrian Colyer,
+ * ******************************************************************/
+package org.aspectj.testing;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.aspectj.testing.util.TestUtil;
+import org.aspectj.tools.ajc.AjcTestCase;
+import org.aspectj.tools.ajc.CompilationResult;
+
+/**
+ * @author Adrian Colyer
+ * @author Andy Clement
+ */
+public class CompileSpec implements ITestStep {
+
+ private List<ExpectedMessageSpec> expected = new ArrayList<ExpectedMessageSpec>();
+
+ private String files;
+ private boolean includeClassesDir;
+ private String aspectpath;
+ private String classpath;
+ private String modulepath;
+ private String inpath;
+ private String sourceroots;
+ private String outjar;
+ private String outxml;
+ private String xlintfile;
+ private String options;
+ private String baseDir;
+ private String extdirs;
+ private AjcTest myTest;
+
+ public CompileSpec() {
+ }
+
+ public void execute(AjcTestCase inTestCase) {
+ File base = new File(baseDir);
+ String[] args = buildArgs();
+ CompilationResult result = inTestCase.ajc(base,args);
+ AjcTestCase.MessageSpec messageSpec = buildMessageSpec();
+ String failMessage = "test \"" + myTest.getTitle() + "\" failed";
+ inTestCase.assertMessages(result,failMessage,messageSpec);
+ inTestCase.setShouldEmptySandbox(false); // so subsequent steps in same test see my results
+ }
+
+ public void addExpectedMessage(ExpectedMessageSpec message) {
+ expected.add(message);
+ }
+
+ public void setBaseDir(String dir) {
+ this.baseDir = dir;
+ }
+
+ protected String getBaseDir() { return baseDir; }
+
+ public void setTest(AjcTest t) {
+ this.myTest = t;
+ if (options != null && (options.indexOf("-1.5") != -1)) {
+ myTest.setVm("1.5");
+ }
+ }
+
+ protected AjcTest getTest() { return myTest; }
+
+ /**
+ * @return Returns the aspectpath.
+ */
+ public String getAspectpath() {
+ return aspectpath;
+ }
+ /**
+ * @param aspectpath The aspectpath to set.
+ */
+ public void setAspectpath(String aspectpath) {
+ this.aspectpath = aspectpath.replace(',',File.pathSeparatorChar);
+ }
+ /**
+ * @return Returns the classpath.
+ */
+ public String getClasspath() {
+ return classpath;
+ }
+ /**
+ * @param classpath The classpath to set.
+ */
+ public void setClasspath(String classpath) {
+ this.classpath = classpath.replace(',',File.pathSeparatorChar);
+ }
+
+ public String getModulepath() {
+ return this.modulepath;
+ }
+
+ public void setModulepath(String modulepath) {
+ this.modulepath = modulepath.replace(',', File.pathSeparatorChar);
+ }
+ /**
+ * @return Returns the files.
+ */
+ public String getFiles() {
+ return files;
+ }
+ /**
+ * @param files The files to set.
+ */
+ public void setFiles(String files) {
+ this.files = files;
+ }
+ /**
+ * @return Returns the includeClassesDir.
+ */
+ public boolean isIncludeClassesDir() {
+ return includeClassesDir;
+ }
+ /**
+ * @param includeClassesDir The includeClassesDir to set.
+ */
+ public void setIncludeClassesDir(boolean includeClassesDir) {
+ this.includeClassesDir = includeClassesDir;
+ }
+ /**
+ * @return Returns the inpath.
+ */
+ public String getInpath() {
+ return inpath;
+ }
+ /**
+ * @param inpath The inpath to set.
+ */
+ public void setInpath(String inpath) {
+ this.inpath = inpath.replace(',',File.pathSeparatorChar).replace(';',File.pathSeparatorChar);
+ }
+ /**
+ * @return Returns the options.
+ */
+ public String getOptions() {
+ return options;
+ }
+ /**
+ * @param options The options to set.
+ */
+ public void setOptions(String options) {
+ int i = options.indexOf("!eclipse");
+ if (i != -1) {
+ this.options = options.substring(0,i);
+ this.options += options.substring(i + "!eclipse".length());
+ } else {
+ this.options = options;
+ }
+ }
+ /**
+ * @return Returns the outjar.
+ */
+ public String getOutjar() {
+ return outjar;
+ }
+ /**
+ * @param outjar The outjar to set.
+ */
+ public void setOutjar(String outjar) {
+ this.outjar = outjar;
+ }
+
+ /**
+ * @return Returns the outxml.
+ */
+ public String getOutxmlfile() {
+ return outxml;
+ }
+
+ /**
+ * @param outxml The the of the aop.xml file to generate
+ */
+ public void setOutxmlfile(String outxml) {
+ this.outxml = outxml;
+ }
+ /**
+ * @return Returns the sourceroots.
+ */
+ public String getSourceroots() {
+ return sourceroots;
+ }
+ /**
+ * @param sourceroots The sourceroots to set.
+ */
+ public void setSourceroots(String sourceroots) {
+ this.sourceroots = sourceroots;
+ }
+ /**
+ * @return Returns the xlintfile.
+ */
+ public String getXlintfile() {
+ return xlintfile;
+ }
+ /**
+ * @param xlintfile The xlintfile to set.
+ */
+ public void setXlintfile(String xlintfile) {
+ this.xlintfile = xlintfile;
+ }
+
+ public String getExtdirs() { return extdirs;}
+ public void setExtdirs(String extdirs) { this.extdirs = extdirs; }
+
+ protected String[] buildArgs() {
+ StringBuffer args = new StringBuffer();
+ // add any set options, and then files to compile at the end
+ if (getAspectpath() != null) {
+ args.append("-aspectpath ");
+ args.append(getAspectpath());
+ args.append(" ");
+ }
+ if (getSourceroots() != null) {
+ args.append("-sourceroots ");
+ args.append(getSourceroots());
+ args.append(" ");
+ }
+ if (getOutjar() != null) {
+ args.append("-outjar ");
+ args.append(getOutjar());
+ args.append(" ");
+ }
+ if (getOutxmlfile() != null) {
+ args.append("-outxmlfile ");
+ args.append(getOutxmlfile());
+ args.append(" ");
+ }
+ if (getOptions() != null) {
+ StringTokenizer strTok = new StringTokenizer(getOptions(),",");
+ while (strTok.hasMoreTokens()) {
+ // For an option containing a comma, pass in a { in its place
+ args.append(strTok.nextToken().replace('{', ','));
+ args.append(" ");
+ }
+ }
+ if (getClasspath() != null) {
+ args.append("-classpath ");
+ args.append(getClasspath());
+ args.append(" ");
+ }
+ if (getModulepath() != null) {
+ args.append("-p ");
+ args.append(rewrite(getModulepath()));
+ args.append(" ");
+ }
+ if (getXlintfile() != null) {
+ args.append("-Xlintfile ");
+ args.append(getXlintfile());
+ args.append(" ");
+ }
+ if (getExtdirs() != null) {
+ args.append("-extdirs ");
+ args.append(getExtdirs());
+ args.append(" ");
+ }
+ List<String> fileList = new ArrayList<String>();
+ List<String> jarList = new ArrayList<String>();
+ // convention that any jar on file list should be added to inpath
+ String files = getFiles();
+ if (files == null) files = "";
+ StringTokenizer strTok = new StringTokenizer(files,",");
+ while (strTok.hasMoreTokens()) {
+ final String file = strTok.nextToken();
+ if (file.endsWith(".jar")) {
+ jarList.add(file);
+ } else {
+ fileList.add(file);
+ }
+ }
+ if ((getInpath() != null) || !jarList.isEmpty()) {
+ args.append("-inpath ");
+ if (getInpath() != null) args.append(getInpath());
+ for (String jar: jarList) {
+ args.append(File.pathSeparator);
+ args.append(jar);
+ }
+ args.append(" ");
+ }
+ for (String file: fileList) {
+ args.append(file);
+ args.append(" ");
+ }
+ String argumentString = args.toString();
+ strTok = new StringTokenizer(argumentString," ");
+ String[] ret = new String[strTok.countTokens()];
+ for (int i = 0; i < ret.length; i++) {
+ ret[i] = strTok.nextToken();
+ }
+ return ret;
+ }
+
+ private String rewrite(String path) {
+ path = path.replace("$runtime", TestUtil.aspectjrtPath().toString());
+ return path;
+ }
+
+ protected AjcTestCase.MessageSpec buildMessageSpec() {
+ List<AjcTestCase.Message> infos = null;
+ List<AjcTestCase.Message> warnings = new ArrayList<AjcTestCase.Message>();
+ List<AjcTestCase.Message> errors = new ArrayList<AjcTestCase.Message>();
+ List<AjcTestCase.Message> fails = new ArrayList<AjcTestCase.Message>();
+ List<AjcTestCase.Message> weaveInfos = new ArrayList<AjcTestCase.Message>();
+ for (ExpectedMessageSpec exMsg: expected) {
+ String kind = exMsg.getKind();
+ if (kind.equals("info")) {
+ if (infos == null) infos = new ArrayList<AjcTestCase.Message>();
+ infos.add(exMsg.toMessage());
+ } else if (kind.equals("warning")) {
+ warnings.add(exMsg.toMessage());
+ } else if (kind.equals("error")) {
+ errors.add(exMsg.toMessage());
+ } else if (kind.equals("fail")) {
+ fails.add(exMsg.toMessage());
+ } else if (kind.equals("abort")) {
+ fails.add(exMsg.toMessage());
+ } else if (kind.equals("weave")) {
+ weaveInfos.add(exMsg.toMessage());
+ }
+ }
+ return new AjcTestCase.MessageSpec(infos,warnings,errors,fails,weaveInfos);
+ }
+
+}
diff --git a/testing/src/test/java/org/aspectj/testing/ExpectedMessageSpec.java b/testing/src/test/java/org/aspectj/testing/ExpectedMessageSpec.java
new file mode 100644
index 000000000..2193b55ae
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/ExpectedMessageSpec.java
@@ -0,0 +1,94 @@
+/* *******************************************************************
+ * Copyright (c) 2004 IBM Corporation
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Adrian Colyer,
+ * ******************************************************************/
+package org.aspectj.testing;
+
+import org.aspectj.tools.ajc.AjcTestCase;
+
+/**
+ * @author colyer
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class ExpectedMessageSpec {
+
+ private String kind = "error";
+ private int line = -1;
+ private String text;
+ private String file;
+ private String details;
+
+ public AjcTestCase.Message toMessage() {
+ return new AjcTestCase.Message(line,file,text,null);
+ }
+
+ /**
+ * @return Returns the details.
+ */
+ public String getDetails() {
+ return details;
+ }
+ /**
+ * @param details The details to set.
+ */
+ public void setDetails(String details) {
+ this.details = details;
+ }
+ /**
+ * @return Returns the file.
+ */
+ public String getFile() {
+ return file;
+ }
+ /**
+ * @param file The file to set.
+ */
+ public void setFile(String file) {
+ this.file = file;
+ }
+ /**
+ * @return Returns the kind.
+ */
+ public String getKind() {
+ return kind;
+ }
+ /**
+ * @param kind The kind to set.
+ */
+ public void setKind(String kind) {
+ this.kind = kind;
+ }
+ /**
+ * @return Returns the line.
+ */
+ public int getLine() {
+ return line;
+ }
+ /**
+ * @param line The line to set.
+ */
+ public void setLine(int line) {
+ this.line = line;
+ }
+ /**
+ * @return Returns the text.
+ */
+ public String getText() {
+ return text;
+ }
+ /**
+ * @param text The text to set.
+ */
+ public void setText(String text) {
+ this.text = text;
+ }
+}
diff --git a/testing/src/test/java/org/aspectj/testing/FileSpec.java b/testing/src/test/java/org/aspectj/testing/FileSpec.java
new file mode 100644
index 000000000..2164f38f1
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/FileSpec.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Contributors
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Andy Clement - SpringSource
+ *******************************************************************************/
+package org.aspectj.testing;
+
+import java.io.File;
+
+import org.aspectj.tools.ajc.AjcTestCase;
+
+/**
+ * Support simple file system operations in a test spec. Example:<br>
+ * &lt;file deletefile="foo.jar"/&gt; will delete the file foo.jar from the sandbox.
+ *
+ * @author Andy Clement
+ */
+public class FileSpec implements ITestStep {
+
+ private String toDelete;
+
+ private String renameFrom;
+ private String renameTo;
+
+ // private String dir;
+ // private AjcTest test;
+
+ public FileSpec() {
+ }
+
+ public void setRenameFrom(String file) {
+ this.renameFrom = file;
+ }
+
+ public void setRenameTo(String file) {
+ this.renameTo = file;
+ }
+
+ public void setDeletefile(String file) {
+ this.toDelete = file;
+ }
+
+ @Override
+ public void addExpectedMessage(ExpectedMessageSpec message) {
+ }
+
+ @Override
+ public void execute(AjcTestCase inTestCase) {
+ File sandbox = inTestCase.getSandboxDirectory();
+ if (toDelete != null) {
+ File targetForDeletion = new File(sandbox, toDelete);
+ if (targetForDeletion.isFile()) {
+ targetForDeletion.delete();
+ } else {
+ recursiveDelete(targetForDeletion);
+ }
+ }
+ if (renameFrom != null) {
+ if (renameTo == null) {
+ throw new IllegalStateException("If setting renameFrom the renameTo should also be set");
+ }
+ File fileFrom = new File(sandbox, renameFrom);
+ File fileTo = new File(sandbox, renameTo);
+ fileFrom.renameTo(fileTo);
+ }
+ }
+
+ private void recursiveDelete(File toDelete) {
+ if (toDelete.isDirectory()) {
+ File[] files = toDelete.listFiles();
+ for (File f: files) {
+ recursiveDelete(f);
+ }
+ }
+ toDelete.delete();
+ }
+
+ @Override
+ public void setBaseDir(String dir) {
+ // this.dir = dir;
+ }
+
+ @Override
+ public void setTest(AjcTest test) {
+ // this.test = test;
+ }
+
+}
diff --git a/testing/src/test/java/org/aspectj/testing/ITestStep.java b/testing/src/test/java/org/aspectj/testing/ITestStep.java
new file mode 100644
index 000000000..673d6c66d
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/ITestStep.java
@@ -0,0 +1,28 @@
+/* *******************************************************************
+ * Copyright (c) 2004 IBM Corporation
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Adrian Colyer,
+ * ******************************************************************/
+package org.aspectj.testing;
+
+import org.aspectj.tools.ajc.AjcTestCase;
+
+/**
+ * @author Adrian Colyer
+ */
+public interface ITestStep {
+
+ void execute(AjcTestCase inTestCase);
+
+ void addExpectedMessage(ExpectedMessageSpec message);
+
+ void setBaseDir(String dir);
+
+ void setTest(AjcTest test);
+}
diff --git a/testing/src/test/java/org/aspectj/testing/MakeTestClass.java b/testing/src/test/java/org/aspectj/testing/MakeTestClass.java
new file mode 100644
index 000000000..721cb623b
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/MakeTestClass.java
@@ -0,0 +1,118 @@
+/*
+ * Created on 02-Aug-2004
+ *
+ */
+package org.aspectj.testing;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.digester.Digester;
+
+/**
+ * @author colyer
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class MakeTestClass {
+
+ private static final String HEADER =
+ "/* *******************************************************************\n" +
+ " * Copyright (c) 2004 IBM Corporation\n" +
+ " * All rights reserved.\n" +
+ " * This program and the accompanying materials are made available\n" +
+ " * under the terms of the Eclipse Public License v1.0\n" +
+ " * which accompanies this distribution and is available at\n" +
+ " * http://www.eclipse.org/legal/epl-v10.html \n" +
+ " * \n" +
+ " * ******************************************************************/\n" +
+ "package org.aspectj.systemtest.XXX;\n" +
+ "\n" +
+ "import java.io.File;\n" +
+ "import junit.framework.Test;\n" +
+ "import org.aspectj.testing.XMLBasedAjcTestCase;\n" +
+ "\n" +
+ "public class ";
+
+ private static final String BODY_1 =
+ " extends org.aspectj.testing.XMLBasedAjcTestCase {\n" +
+ "\n" +
+ " public static Test suite() {\n" +
+ " return XMLBasedAjcTestCase.loadSuite(";
+
+ private static final String BODY_2 =
+ ".class);\n" +
+ " }\n" +
+ "\n" +
+ " protected File getSpecFile() {\n" +
+ " return new File(\"";
+
+ private static final String BODY_3 =
+ "\");\n" +
+ " }\n";
+
+ private static final String FOOTER =
+ "}\n";
+
+ private List<AjcTest> tests = new ArrayList<AjcTest>();
+ private String className;
+ private String suiteFile;
+
+ public static void main(String[] args) throws Exception {
+ new MakeTestClass(args[0],args[1]).makeTestClass();
+ }
+
+ public MakeTestClass(String className, String suiteFile)throws Exception {
+ this.className = className;
+ this.suiteFile = suiteFile;
+ Digester d = getDigester();
+ InputStreamReader isr = new InputStreamReader(new FileInputStream(suiteFile));
+ d.parse(isr);
+ }
+
+ public void addTest(AjcTest test) {
+ tests.add(test);
+ }
+
+ public void makeTestClass() throws Exception {
+ FileOutputStream fos = new FileOutputStream(className + ".java");
+ PrintStream out = new PrintStream(fos);
+ out.print(HEADER);
+ out.print(className);
+ out.print(BODY_1);
+ out.print(className);
+ out.print(BODY_2);
+ out.print(suiteFile);
+ out.print(BODY_3);
+ out.println();
+ int testNo = 1;
+ NumberFormat nf = NumberFormat.getInstance();
+ nf.setMinimumIntegerDigits(3);
+ for (AjcTest test: tests) {
+ out.println();
+ out.print(" public void test");
+ out.print(nf.format(testNo++));
+ out.println("(){");
+ out.println(" runTest(\"" + test.getTitle() + "\");");
+ out.println(" }");
+ }
+ out.println();
+ out.println(FOOTER);
+ out.close();
+ }
+
+ private Digester getDigester() {
+ Digester digester = new Digester();
+ digester.push(this);
+ digester.addObjectCreate("suite/ajc-test",AjcTest.class);
+ digester.addSetProperties("suite/ajc-test");
+ digester.addSetNext("suite/ajc-test","addTest","org.aspectj.testing.AjcTest");
+ return digester;
+ }
+}
diff --git a/testing/src/test/java/org/aspectj/testing/OutputLine.java b/testing/src/test/java/org/aspectj/testing/OutputLine.java
new file mode 100644
index 000000000..fc6fe4ce9
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/OutputLine.java
@@ -0,0 +1,42 @@
+/* *******************************************************************
+ * Copyright (c) 2005 IBM Corporation
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Adrian Colyer,
+ * ******************************************************************/
+package org.aspectj.testing;
+
+/**
+ * @author Adrian Colyer
+ * @author Andy Clement
+ */
+public class OutputLine {
+
+ // Expected text
+ private String text;
+
+ // Comma separated list of vm versions on which this is expected
+ private String vm;
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ public String getVm() {
+ return vm;
+ }
+
+ public void setVm(String vm) {
+ this.vm = vm;
+ }
+
+}
diff --git a/testing/src/test/java/org/aspectj/testing/OutputSpec.java b/testing/src/test/java/org/aspectj/testing/OutputSpec.java
new file mode 100644
index 000000000..9eab46098
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/OutputSpec.java
@@ -0,0 +1,134 @@
+/* *******************************************************************
+ * Copyright (c) 2005 IBM Corporation
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Adrian Colyer,
+ * ******************************************************************/
+package org.aspectj.testing;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+
+import org.aspectj.tools.ajc.AjcTestCase;
+import org.aspectj.util.LangUtil;
+
+public class OutputSpec {
+
+ private List<String> expectedOutputLines = new ArrayList<String>();
+
+ public void addLine(OutputLine line) {
+ if (line.getVm() == null || matchesThisVm(line.getVm())) {
+ expectedOutputLines.add(line.getText());
+ }
+ }
+
+ /**
+ * For a test output line that has specified a vm version, check if it matches the vm we are running on.
+ * vm might be "1.2,1.3,1.4,1.5" or simply "9" or it may be a version with a '+' suffix indicating that
+ * level or later, e.g. "9+" should be ok on Java 10
+ * @return true if the current vm version matches the spec
+ */
+ private boolean matchesThisVm(String vm) {
+ // vm might be 1.2, 1.3, 1.4, 1.5 or 1.9 possibly with a '+' in there
+ // For now assume + is attached to there only being one version, like "9+"
+ if (vm.contains(LangUtil.getVmVersionString())) {
+ return true;
+ }
+ if (vm.endsWith("+")) {
+ double vmVersion = LangUtil.getVmVersion();
+ double vmSpecified = Double.parseDouble(vm.substring(0,vm.length()-1));
+ return vmVersion >= vmSpecified;
+ }
+ return false;
+ }
+
+ public void matchAgainst(String output) {
+ matchAgainst(output, "yes");
+ }
+
+ public void matchAgainst(String output, String ordered) {
+ if (ordered != null && ordered.equals("no")) {
+ unorderedMatchAgainst(output);
+ return;
+ }
+ boolean matches = false;
+ int lineNo = 0;
+ StringTokenizer strTok = new StringTokenizer(output,"\n");
+ if (strTok.countTokens() == expectedOutputLines.size()) {
+ matches = true;
+ for (String line: expectedOutputLines) {
+ lineNo++;
+ String outputLine = strTok.nextToken().trim();
+ /* Avoid trying to match on ajSandbox source names that appear in messages */
+ if (outputLine.indexOf(line) == -1) {
+ matches = false;
+ break;
+ }
+ }
+ } else { lineNo = -1; }
+ if (!matches) {
+ createFailureMessage(output, lineNo, strTok.countTokens());
+ }
+ }
+
+ public void unorderedMatchAgainst(String output) {
+ List<String> outputFound = getOutputFound(output);
+ if(outputFound.size() != expectedOutputLines.size()) {
+ createFailureMessage(output, -1, outputFound.size());
+ return;
+ }
+ List<String> expected = new ArrayList<String>();
+ expected.addAll(expectedOutputLines);
+ List<String> found = new ArrayList<String>();
+ found.addAll(outputFound);
+ for (Iterator<String> iterator = outputFound.iterator(); iterator.hasNext();) {
+ String lineFound = (String) iterator.next();
+ for (Iterator<String> iterator2 = expectedOutputLines.iterator(); iterator2.hasNext();) {
+ String lineExpected = (String) iterator2.next();
+ if (lineFound.indexOf(lineExpected)!= -1) {
+ found.remove(lineFound);
+ expected.remove(lineExpected);
+ continue;
+ }
+ }
+ }
+ if (!found.isEmpty() || !expected.isEmpty()) {
+ createFailureMessage(output,-2,outputFound.size());
+ }
+ }
+
+ private void createFailureMessage(String output, int lineNo, int sizeFound) {
+ StringBuffer failMessage = new StringBuffer();
+ failMessage.append("\n expecting output:\n");
+ for (String line: expectedOutputLines) {
+ failMessage.append(line);
+ failMessage.append("\n");
+ }
+ failMessage.append(" but found output:\n");
+ failMessage.append(output);
+ failMessage.append("\n");
+ if (lineNo==-1) {
+ failMessage.append("Expected "+expectedOutputLines.size()+" lines of output but there are "+sizeFound);
+ } else if (lineNo >= 0) {
+ failMessage.append("First difference is on line " + lineNo);
+ }
+ failMessage.append("\n");
+ AjcTestCase.fail(failMessage.toString());
+ }
+
+ private List<String> getOutputFound(String output) {
+ List<String> found = new ArrayList<String>();
+ StringTokenizer strTok = new StringTokenizer(output,"\n");
+ while(strTok.hasMoreTokens()) {
+ String outputLine = strTok.nextToken().trim();
+ found.add(outputLine);
+ }
+ return found;
+ }
+}
diff --git a/testing/src/test/java/org/aspectj/testing/RunSpec.java b/testing/src/test/java/org/aspectj/testing/RunSpec.java
new file mode 100644
index 000000000..62b93bcb0
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/RunSpec.java
@@ -0,0 +1,262 @@
+/* *******************************************************************
+ * Copyright (c) 2004 IBM Corporation
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Adrian Colyer, Abraham Nevado (lucierna)
+ * ******************************************************************/
+package org.aspectj.testing;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import org.aspectj.tools.ajc.AjcTestCase;
+import org.aspectj.util.FileUtil;
+
+/**
+ * @author Adrian Colyer
+ */
+public class RunSpec implements ITestStep {
+
+ private List<ExpectedMessageSpec> expected = new ArrayList<ExpectedMessageSpec>();
+ private String classToRun;
+ private String moduleToRun; // alternative to classToRun on JDK9+
+ private String baseDir;
+ private String options;
+ private String cpath;
+ private String mpath;
+ private String orderedStderr;
+ private AjcTest myTest;
+ private OutputSpec stdErrSpec;
+ private OutputSpec stdOutSpec;
+ private String ltwFile;
+ private String xlintFile;
+ private String vmargs;
+ private String usefullltw;
+
+ @Override
+ public String toString() {
+ return "RunSpec: Running '"+classToRun+"' in directory '"+baseDir+"'. Classpath of '"+cpath+"'";
+ }
+ public RunSpec() {
+ }
+
+ @Override
+ public void execute(AjcTestCase inTestCase) {
+ if (!expected.isEmpty()) {
+ System.err.println("Warning, message spec for run command is currently ignored (org.aspectj.testing.RunSpec)");
+ }
+ String[] args = buildArgs();
+// System.err.println("? execute() inTestCase='" + inTestCase + "', ltwFile=" + ltwFile);
+ boolean useLtw = copyLtwFile(inTestCase.getSandboxDirectory());
+
+ copyXlintFile(inTestCase.getSandboxDirectory());
+ try {
+ setSystemProperty("test.base.dir", inTestCase.getSandboxDirectory().getAbsolutePath());
+
+ AjcTestCase.RunResult rr = inTestCase.run(getClassToRun(), getModuleToRun(), args, vmargs, getClasspath(), getModulepath(), useLtw, "true".equalsIgnoreCase(usefullltw));
+
+ if (stdErrSpec != null) {
+ stdErrSpec.matchAgainst(rr.getStdErr(), orderedStderr);
+ }
+ if (stdOutSpec != null) {
+ stdOutSpec.matchAgainst(rr.getStdOut());
+ }
+ } finally {
+ restoreProperties();
+ }
+ }
+
+ /*
+ * Logic to save/restore system properties. Copied from LTWTests. As Matthew noted, need to refactor LTWTests to use this
+ */
+
+ private Properties savedProperties = new Properties();
+
+ public void setSystemProperty(String key, String value) {
+ Properties systemProperties = System.getProperties();
+ copyProperty(key, systemProperties, savedProperties);
+ systemProperties.setProperty(key, value);
+ }
+
+ private static void copyProperty(String key, Properties from, Properties to) {
+ String value = from.getProperty(key, NULL);
+ to.setProperty(key, value);
+ }
+
+ private final static String NULL = "null";
+
+ protected void restoreProperties() {
+ Properties systemProperties = System.getProperties();
+ for (Enumeration<Object> enu = savedProperties.keys(); enu.hasMoreElements();) {
+ String key = (String) enu.nextElement();
+ String value = savedProperties.getProperty(key);
+ if (value == NULL)
+ systemProperties.remove(key);
+ else
+ systemProperties.setProperty(key, value);
+ }
+ }
+
+ @Override
+ public void addExpectedMessage(ExpectedMessageSpec message) {
+ expected.add(message);
+ }
+
+ @Override
+ public void setBaseDir(String dir) {
+ this.baseDir = dir;
+ }
+
+ @Override
+ public void setTest(AjcTest test) {
+ this.myTest = test;
+ }
+
+ public AjcTest getTest() {
+ return this.myTest;
+ }
+
+ public String getOptions() {
+ return options;
+ }
+
+ public void setOptions(String options) {
+ this.options = options;
+ }
+
+ public String getClasspath() {
+ if (cpath == null)
+ return null;
+ return this.cpath.replace('/', File.separatorChar).replace(',', File.pathSeparatorChar);
+ }
+
+ public String getModulepath() {
+ if (mpath == null)
+ return null;
+ return this.mpath.replace('/', File.separatorChar).replace(',', File.pathSeparatorChar);
+ }
+
+ public void setModulepath(String mpath) {
+ this.mpath = mpath;
+ }
+
+ public void setClasspath(String cpath) {
+ this.cpath = cpath;
+ }
+
+ public void addStdErrSpec(OutputSpec spec) {
+ this.stdErrSpec = spec;
+ }
+
+ public void addStdOutSpec(OutputSpec spec) {
+ this.stdOutSpec = spec;
+ }
+
+ public void setOrderedStderr(String orderedStderr) {
+ this.orderedStderr = orderedStderr;
+ }
+
+ public String getClassToRun() {
+ return classToRun;
+ }
+
+ public void setClassToRun(String classToRun) {
+ this.classToRun = classToRun;
+ }
+
+ public void setModuleToRun(String moduleToRun) {
+ this.moduleToRun = moduleToRun;
+ }
+
+ public String getModuleToRun() {
+ return this.moduleToRun;
+ }
+
+ public String getLtwFile() {
+ return ltwFile;
+ }
+
+ public void setLtwFile(String ltwFile) {
+ this.ltwFile = ltwFile;
+ }
+
+ private String[] buildArgs() {
+ if (options == null)
+ return new String[0];
+ StringTokenizer strTok = new StringTokenizer(options, ",");
+ String[] ret = new String[strTok.countTokens()];
+ for (int i = 0; i < ret.length; i++) {
+ ret[i] = strTok.nextToken();
+ }
+ return ret;
+ }
+
+ private boolean copyLtwFile(File sandboxDirectory) {
+ boolean useLtw = false;
+
+ if (ltwFile != null) {
+ // TODO maw use flag rather than empty file name
+ if (ltwFile.trim().length() == 0)
+ return true;
+
+ File from = new File(baseDir, ltwFile);
+ File to = new File(sandboxDirectory, "META-INF" + File.separator + "aop.xml");
+ // System.out.println("RunSpec.copyLtwFile() from=" + from.getAbsolutePath() + " to=" + to.getAbsolutePath());
+ try {
+ FileUtil.copyFile(from, to);
+ useLtw = true;
+ } catch (IOException ex) {
+ AjcTestCase.fail(ex.toString());
+ }
+ }
+
+ return useLtw;
+ }
+
+ public String getXlintFile() {
+ return xlintFile;
+ }
+
+ public void setXlintFile(String xlintFile) {
+ this.xlintFile = xlintFile;
+ }
+
+ public void setVmargs(String vmargs) {
+ this.vmargs = vmargs;
+ }
+
+ public String getVmargs() {
+ return vmargs;
+ }
+
+
+ public String getUsefullltw() {
+ return usefullltw;
+ }
+
+ public void setUsefullltw(String usefullltw) {
+ this.usefullltw = usefullltw;
+ }
+
+ private void copyXlintFile(File sandboxDirectory) {
+ if (xlintFile != null) {
+ File from = new File(baseDir, xlintFile);
+ File to = new File(sandboxDirectory, File.separator + xlintFile);
+ try {
+ FileUtil.copyFile(from, to);
+ } catch (IOException ex) {
+ AjcTestCase.fail(ex.toString());
+ }
+ }
+ }
+}
diff --git a/testing/src/test/java/org/aspectj/testing/TestingModuleTests.java b/testing/src/test/java/org/aspectj/testing/TestingModuleTests.java
deleted file mode 100644
index b6c20b215..000000000
--- a/testing/src/test/java/org/aspectj/testing/TestingModuleTests.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.aspectj.testing;
-/* *******************************************************************
- * 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 Eclipse Public License v1.0
- * which accompanies this distribution and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- * Xerox/PARC initial implementation
- * ******************************************************************/
-
-
-// default package
-
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-
-public class TestingModuleTests extends TestCase {
-
- public static Test suite() {
- TestSuite suite = new TestSuite(TestingModuleTests.class.getName());
- suite.addTest(org.aspectj.testing.harness.bridge.TestingBridgeTests.suite());
- suite.addTest(org.aspectj.testing.taskdefs.TaskdefTests.suite());
- suite.addTest(org.aspectj.testing.util.UtilTests.suite());
- suite.addTest(org.aspectj.testing.util.options.OptionsTests.suite());
- suite.addTest(org.aspectj.testing.xml.TestingXmlTests.suite());
- return suite;
- }
-
- public TestingModuleTests(String name) { super(name); }
-
-}
diff --git a/testing/src/test/java/org/aspectj/testing/WeaveSpec.java b/testing/src/test/java/org/aspectj/testing/WeaveSpec.java
new file mode 100644
index 000000000..2793b63ac
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/WeaveSpec.java
@@ -0,0 +1,164 @@
+/* *******************************************************************
+ * Copyright (c) 2005 IBM Corporation
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Adrian Colyer,
+ * ******************************************************************/
+package org.aspectj.testing;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
+import org.aspectj.tools.ajc.AjcTestCase;
+import org.aspectj.tools.ajc.CompilationResult;
+
+/**
+ * @author colyer
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class WeaveSpec extends CompileSpec {
+
+ private String classesFiles;
+ private String aspectsFiles;
+ private List<File> classFilesFromClasses;
+
+ /* (non-Javadoc)
+ * @see org.aspectj.testing.ITestStep#execute(org.aspectj.tools.ajc.AjcTestCase)
+ */
+ public void execute(AjcTestCase inTestCase) {
+ String failMessage = "test \"" + getTest().getTitle() + "\" failed";
+ try {
+ File base = new File(getBaseDir());
+ classFilesFromClasses = new ArrayList<File>();
+ setFiles(classesFiles);
+ String[] args = buildArgs();
+ CompilationResult result = inTestCase.ajc(base,args);
+ inTestCase.assertNoMessages(result,failMessage);
+ File sandbox = inTestCase.getSandboxDirectory();
+ createJar(sandbox,"classes.jar",true);
+
+ inTestCase.setShouldEmptySandbox(false);
+ setFiles(aspectsFiles);
+ String options = getOptions();
+ if (options == null) {
+ setOptions("");
+ }
+ setClasspath("classes.jar");
+ args = buildArgs();
+ result = inTestCase.ajc(base,args);
+ inTestCase.assertNoMessages(result,failMessage);
+ createJar(sandbox,"aspects.jar",false);
+
+ args = buildWeaveArgs();
+ inTestCase.setShouldEmptySandbox(false);
+ result = inTestCase.ajc(base,args);
+ AjcTestCase.MessageSpec messageSpec = buildMessageSpec();
+ inTestCase.assertMessages(result,failMessage,messageSpec);
+ inTestCase.setShouldEmptySandbox(false); // so subsequent steps in same test see my results
+ } catch (IOException e) {
+ AjcTestCase.fail(failMessage + " " + e);
+ }
+ }
+
+ public void setClassesFiles(String files) {
+ this.classesFiles = files;
+ }
+
+ public void setAspectsFiles(String files) {
+ this.aspectsFiles = files;
+ }
+
+ /**
+ * Find all the .class files under the dir, package them into a jar file,
+ * and then delete them.
+ * @param inDir
+ * @param name
+ */
+ private void createJar(File inDir, String name, boolean isClasses) throws IOException {
+ File outJar = new File(inDir,name);
+ FileOutputStream fos = new FileOutputStream(outJar);
+ JarOutputStream jarOut = new JarOutputStream(fos);
+ List<File> classFiles = new ArrayList<File>();
+ List<File> toExclude = isClasses ? Collections.<File>emptyList() : classFilesFromClasses;
+ collectClassFiles(inDir,classFiles,toExclude);
+ if (isClasses) classFilesFromClasses = classFiles;
+ String prefix = inDir.getPath() + File.separator;
+ for (File f: classFiles) {
+ String thisPath = f.getPath();
+ if (thisPath.startsWith(prefix)) {
+ thisPath = thisPath.substring(prefix.length());
+ }
+ JarEntry entry = new JarEntry(thisPath);
+ jarOut.putNextEntry(entry);
+ copyFile(f,jarOut);
+ jarOut.closeEntry();
+ }
+ jarOut.flush();
+ jarOut.close();
+ }
+
+ private void collectClassFiles(File inDir, List<File> inList, List<File> toExclude) {
+ File[] contents = inDir.listFiles();
+ for (int i = 0; i < contents.length; i++) {
+ if (contents[i].getName().endsWith(".class")) {
+ if (!toExclude.contains(contents[i])) {
+ inList.add(contents[i]);
+ }
+ } else if (contents[i].isDirectory()) {
+ collectClassFiles(contents[i],inList, toExclude);
+ }
+ }
+ }
+
+ private void copyFile(File f, OutputStream dest) throws IOException {
+ FileInputStream fis = new FileInputStream(f);
+ byte[] buf = new byte[4096];
+ int read = -1;
+ while((read = fis.read(buf)) != -1) {
+ dest.write(buf,0,read);
+ }
+ fis.close();
+ }
+
+ private String[] buildWeaveArgs() {
+ StringBuffer args = new StringBuffer();
+ if (getOptions() != null) {
+ StringTokenizer strTok = new StringTokenizer(getOptions(),",");
+ while (strTok.hasMoreTokens()) {
+ args.append(strTok.nextToken());
+ args.append(" ");
+ }
+ }
+ args.append("-inpath ");
+ args.append("classes.jar");
+ args.append(File.pathSeparator);
+ args.append("aspects.jar");
+ args.append(" ");
+ args.append("-aspectpath ");
+ args.append("aspects.jar");
+ String argumentString = args.toString();
+ StringTokenizer strTok = new StringTokenizer(argumentString," ");
+ String[] ret = new String[strTok.countTokens()];
+ for (int i = 0; i < ret.length; i++) {
+ ret[i] = strTok.nextToken();
+ }
+ return ret;
+ }
+
+}
diff --git a/testing/src/test/java/org/aspectj/testing/XMLBasedAjcTestCase.java b/testing/src/test/java/org/aspectj/testing/XMLBasedAjcTestCase.java
new file mode 100644
index 000000000..13bbf844e
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/XMLBasedAjcTestCase.java
@@ -0,0 +1,522 @@
+/* *******************************************************************
+ * Copyright (c) 2004 IBM Corporation
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Adrian Colyer,
+ * ******************************************************************/
+package org.aspectj.testing;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FilenameFilter;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+import org.apache.commons.digester.Digester;
+import org.aspectj.apache.bcel.classfile.Attribute;
+import org.aspectj.apache.bcel.classfile.JavaClass;
+import org.aspectj.apache.bcel.classfile.LocalVariable;
+import org.aspectj.apache.bcel.classfile.LocalVariableTable;
+import org.aspectj.apache.bcel.classfile.Method;
+import org.aspectj.apache.bcel.util.ClassPath;
+import org.aspectj.apache.bcel.util.SyntheticRepository;
+import org.aspectj.tools.ajc.AjcTestCase;
+import org.aspectj.tools.ajc.CompilationResult;
+import org.aspectj.util.FileUtil;
+import org.aspectj.weaver.AjAttribute;
+import org.aspectj.weaver.ResolvedMember;
+import org.aspectj.weaver.ResolvedType;
+import org.aspectj.weaver.WeaverStateInfo;
+import org.aspectj.weaver.AjAttribute.WeaverState;
+import org.aspectj.weaver.AjAttribute.WeaverVersionInfo;
+import org.aspectj.weaver.bcel.BcelConstantPoolReader;
+
+import junit.extensions.TestSetup;
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Root class for all Test suites that are based on an AspectJ XML test suite file. Extends AjcTestCase allowing a mix of
+ * programmatic and spec-file driven testing. See org.aspectj.systemtest.incremental.IncrementalTests for an example of this mixed
+ * style.
+ * <p>
+ * The class org.aspectj.testing.MakeTestClass will generate a subclass of this class for you, given a suite spec. file as input...
+ * </p>
+ */
+public abstract class XMLBasedAjcTestCase extends AjcTestCase {
+
+ private static Map<String,AjcTest> testMap = new HashMap<String,AjcTest>();
+ private static boolean suiteLoaded = false;
+ private AjcTest currentTest = null;
+ private Stack<Boolean> clearTestAfterRun = new Stack<Boolean>();
+
+ public XMLBasedAjcTestCase() {
+ }
+
+ /**
+ * You must define a suite() method in subclasses, and return the result of calling this method. (Don't you hate static methods
+ * in programming models). For example:
+ *
+ * <pre>
+ * public static Test suite() {
+ * return XMLBasedAjcTestCase.loadSuite(MyTestCaseClass.class);
+ * }
+ * </pre>
+ *
+ * @param testCaseClass
+ * @return
+ */
+ public static Test loadSuite(Class<?> testCaseClass) {
+ TestSuite suite = new TestSuite(testCaseClass.getName());
+ suite.addTestSuite(testCaseClass);
+ TestSetup wrapper = new TestSetup(suite) {
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.extensions.TestSetup#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ suiteLoaded = false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.extensions.TestSetup#tearDown()
+ */
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ suiteLoaded = false;
+ }
+ };
+ return wrapper;
+ }
+
+ /**
+ * The file containing the XML specification for the tests.
+ */
+ protected abstract File getSpecFile();
+
+ /*
+ * Return a map from (String) test title -> AjcTest
+ */
+ protected Map<String,AjcTest> getSuiteTests() {
+ return testMap;
+ }
+
+ protected WeaverStateInfo getWeaverStateInfo(JavaClass jc) {
+ WeaverStateInfo wsi = null;
+ try {
+ for (Attribute attribute : jc.getAttributes()) {
+ if (attribute.getName().equals("org.aspectj.weaver.WeaverState")) {
+ if (wsi != null) {
+ fail("Found two WeaverState attributes");
+ }
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ attribute.dump(new DataOutputStream(baos));
+ baos.close();
+ byte[] byteArray = baos.toByteArray();
+ byte[] newbytes = new byte[byteArray.length-6];
+ System.arraycopy(byteArray, 6, newbytes, 0, newbytes.length);
+ WeaverState read = (WeaverState)
+ AjAttribute.read(new WeaverVersionInfo(), WeaverState.AttributeName,
+ newbytes, null, null,
+ new BcelConstantPoolReader(jc.getConstantPool()));
+ wsi = read.reify();
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return wsi;
+ }
+
+ /**
+ * This helper method runs the test with the given title in the suite spec file. All tests steps in given ajc-test execute in
+ * the same sandbox.
+ */
+ protected void runTest(String title, boolean print) {
+ try {
+ currentTest = (AjcTest) testMap.get(title);
+ final boolean clearTest = clearTestAfterRun();
+ if (currentTest == null) {
+ if (clearTest) {
+ System.err.println("test already run: " + title);
+ return;
+ } else {
+ fail("No test '" + title + "' in suite.");
+ }
+ }
+ boolean run = currentTest.runTest(this);
+ assertTrue("Test not run", run);
+ if (clearTest) {
+ testMap.remove(title);
+ }
+ } finally {
+ if (print) {
+ System.out.println("SYSOUT");
+ System.out.println(ajc.getLastCompilationResult().getStandardOutput());
+ }
+ }
+ }
+
+ protected void runTest(String title) {
+ runTest(title, false);
+ }
+
+ /**
+ * Get the currently executing test. Useful for access to e.g. AjcTest.getTitle() etc..
+ */
+ protected AjcTest getCurrentTest() {
+ return currentTest;
+ }
+
+ /**
+ * For use by the Digester. As the XML document is parsed, it creates instances of AjcTest objects, which are added to this
+ * TestCase by the Digester by calling this method.
+ */
+ public void addTest(AjcTest test) {
+ testMap.put(test.getTitle(), test);
+ }
+
+ protected final void pushClearTestAfterRun(boolean val) {
+ clearTestAfterRun.push(val ? Boolean.FALSE : Boolean.TRUE);
+ }
+
+ protected final boolean popClearTestAfterRun() {
+ return clearTest(true);
+ }
+
+ protected final boolean clearTestAfterRun() {
+ return clearTest(false);
+ }
+
+ private boolean clearTest(boolean pop) {
+ if (clearTestAfterRun.isEmpty()) {
+ return false;
+ }
+ boolean result = clearTestAfterRun.peek().booleanValue();
+ if (pop) {
+ clearTestAfterRun.pop();
+ }
+ return result;
+ }
+
+ /*
+ * The rules for parsing a suite spec file. The Digester using bean properties to match attributes in the XML document to
+ * properties in the associated classes, so this simple implementation should be very easy to maintain and extend should you
+ * ever need to.
+ */
+ protected Digester getDigester() {
+ Digester digester = new Digester();
+ digester.push(this);
+ digester.addObjectCreate("suite/ajc-test", AjcTest.class);
+ digester.addSetProperties("suite/ajc-test");
+ digester.addSetNext("suite/ajc-test", "addTest", "org.aspectj.testing.AjcTest");
+ digester.addObjectCreate("suite/ajc-test/compile", CompileSpec.class);
+ digester.addSetProperties("suite/ajc-test/compile");
+ digester.addSetNext("suite/ajc-test/compile", "addTestStep", "org.aspectj.testing.ITestStep");
+ digester.addObjectCreate("suite/ajc-test/file", FileSpec.class);
+ digester.addSetProperties("suite/ajc-test/file");
+ digester.addSetNext("suite/ajc-test/file", "addTestStep", "org.aspectj.testing.ITestStep");
+ digester.addObjectCreate("suite/ajc-test/run", RunSpec.class);
+ digester.addSetProperties("suite/ajc-test/run", "class", "classToRun");
+ digester.addSetProperties("suite/ajc-test/run", "module", "moduleToRun");
+ digester.addSetProperties("suite/ajc-test/run", "ltw", "ltwFile");
+ digester.addSetProperties("suite/ajc-test/run", "xlintfile", "xlintFile");
+ digester.addSetProperties("suite/ajc-test/run/stderr", "ordered", "orderedStderr");
+ digester.addSetNext("suite/ajc-test/run", "addTestStep", "org.aspectj.testing.ITestStep");
+ digester.addObjectCreate("*/message", ExpectedMessageSpec.class);
+ digester.addSetProperties("*/message");
+ digester.addSetNext("*/message", "addExpectedMessage", "org.aspectj.testing.ExpectedMessageSpec");
+ digester.addObjectCreate("suite/ajc-test/weave", WeaveSpec.class);
+ digester.addSetProperties("suite/ajc-test/weave");
+ digester.addSetNext("suite/ajc-test/weave", "addTestStep", "org.aspectj.testing.ITestStep");
+
+ digester.addObjectCreate("suite/ajc-test/ant", AntSpec.class);
+ digester.addSetProperties("suite/ajc-test/ant");
+ digester.addSetNext("suite/ajc-test/ant", "addTestStep", "org.aspectj.testing.ITestStep");
+ digester.addObjectCreate("suite/ajc-test/ant/stderr", OutputSpec.class);
+ digester.addSetProperties("suite/ajc-test/ant/stderr");
+ digester.addSetNext("suite/ajc-test/ant/stderr", "addStdErrSpec", "org.aspectj.testing.OutputSpec");
+ digester.addObjectCreate("suite/ajc-test/ant/stdout", OutputSpec.class);
+ digester.addSetProperties("suite/ajc-test/ant/stdout");
+ digester.addSetNext("suite/ajc-test/ant/stdout", "addStdOutSpec", "org.aspectj.testing.OutputSpec");
+
+ digester.addObjectCreate("suite/ajc-test/run/stderr", OutputSpec.class);
+ digester.addSetProperties("suite/ajc-test/run/stderr");
+ digester.addSetNext("suite/ajc-test/run/stderr", "addStdErrSpec", "org.aspectj.testing.OutputSpec");
+ digester.addObjectCreate("suite/ajc-test/run/stdout", OutputSpec.class);
+ digester.addSetProperties("suite/ajc-test/run/stdout");
+ digester.addSetNext("suite/ajc-test/run/stdout", "addStdOutSpec", "org.aspectj.testing.OutputSpec");
+ digester.addObjectCreate("*/line", OutputLine.class);
+ digester.addSetProperties("*/line");
+ digester.addSetNext("*/line", "addLine", "org.aspectj.testing.OutputLine");
+ return digester;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.aspectj.tools.ajc.AjcTestCase#setUp()
+ */
+ protected void setUp() throws Exception {
+ super.setUp();
+ if (!suiteLoaded) {
+ testMap = new HashMap<String,AjcTest>();
+ System.out.println("LOADING SUITE: " + getSpecFile().getPath());
+ Digester d = getDigester();
+ try {
+ InputStreamReader isr = new InputStreamReader(new FileInputStream(getSpecFile()));
+ d.parse(isr);
+ } catch (Exception ex) {
+ fail("Unable to load suite " + getSpecFile().getPath() + " : " + ex);
+ }
+ suiteLoaded = true;
+ }
+ }
+
+ protected long nextIncrement(boolean doWait) {
+ long time = System.currentTimeMillis();
+ if (doWait) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException intEx) {
+ }
+ }
+ return time;
+ }
+
+ protected void copyFile(String from, String to) throws Exception {
+ String dir = getCurrentTest().getDir();
+ FileUtil.copyFile(new File(dir + File.separator + from), new File(ajc.getSandboxDirectory(), to));
+ }
+
+ protected void copyFileAndDoIncrementalBuild(String from, String to) throws Exception {
+ copyFile(from, to);
+ CompilationResult result = ajc.doIncrementalCompile();
+ assertNoMessages(result, "Expected clean compile from test '" + getCurrentTest().getTitle() + "'");
+ }
+
+ protected void copyFileAndDoIncrementalBuild(String from, String to, MessageSpec expectedResults) throws Exception {
+ String dir = getCurrentTest().getDir();
+ FileUtil.copyFile(new File(dir + File.separator + from), new File(ajc.getSandboxDirectory(), to));
+ CompilationResult result = ajc.doIncrementalCompile();
+ assertMessages(result, "Test '" + getCurrentTest().getTitle() + "' did not produce expected messages", expectedResults);
+ }
+
+ protected void deleteFile(String file) {
+ new File(ajc.getSandboxDirectory(), file).delete();
+ }
+
+ protected void deleteFileAndDoIncrementalBuild(String file, MessageSpec expectedResult) throws Exception {
+ deleteFile(file);
+ CompilationResult result = ajc.doIncrementalCompile();
+ assertMessages(result, "Test '" + getCurrentTest().getTitle() + "' did not produce expected messages", expectedResult);
+ }
+
+ protected void deleteFileAndDoIncrementalBuild(String file) throws Exception {
+ deleteFileAndDoIncrementalBuild(file, MessageSpec.EMPTY_MESSAGE_SET);
+ }
+
+ protected void assertAdded(String file) {
+ assertTrue("File " + file + " should have been added", new File(ajc.getSandboxDirectory(), file).exists());
+ }
+
+ protected void assertDeleted(String file) {
+ assertFalse("File " + file + " should have been deleted", new File(ajc.getSandboxDirectory(), file).exists());
+ }
+
+ protected void assertUpdated(String file, long sinceTime) {
+ File f = new File(ajc.getSandboxDirectory(), file);
+ assertTrue("File " + file + " should have been updated", f.lastModified() > sinceTime);
+ }
+
+ public SyntheticRepository createRepos(File cpentry) {
+ ClassPath cp = new ClassPath(cpentry + File.pathSeparator + System.getProperty("java.class.path"));
+ return SyntheticRepository.getInstance(cp);
+ }
+
+ protected byte[] loadFileAsByteArray(File f) {
+ try {
+ byte[] bs = new byte[100000];
+ BufferedInputStream bis = new BufferedInputStream(new FileInputStream(f));
+ int pos = 0;
+ int len = 0;
+ while ((len=bis.read(bs, pos, 100000-pos))!=-1) {
+ pos+=len;
+ }
+ bis.close();
+ return bs;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ public JavaClass getClassFrom(File where, String clazzname) throws ClassNotFoundException {
+ SyntheticRepository repos = createRepos(where);
+ return repos.loadClass(clazzname);
+ }
+
+ protected Method getMethodStartsWith(JavaClass jc, String prefix) {
+ return getMethodStartsWith(jc,prefix,1);
+ }
+
+ protected Attribute getAttributeStartsWith(Attribute[] attributes, String prefix) {
+ StringBuilder buf = new StringBuilder();
+ for (Attribute a: attributes) {
+ if (a.getName().startsWith(prefix)) {
+ return a;
+ }
+ buf.append(a.toString()).append("\n");
+ }
+ fail("Failed to find '"+prefix+"' in attributes:\n"+buf.toString());
+ return null;
+ }
+
+ protected Method getMethodStartsWith(JavaClass jc, String prefix, int whichone) {
+ Method[] meths = jc.getMethods();
+ for (int i = 0; i < meths.length; i++) {
+ Method method = meths[i];
+ System.out.println(method);
+ if (method.getName().startsWith(prefix)) {
+ whichone--;
+ if (whichone==0) {
+ return method;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Sort it by name then start position
+ */
+ public List<LocalVariable> sortedLocalVariables(LocalVariableTable lvt) {
+ List<LocalVariable> l = new ArrayList<LocalVariable>();
+ LocalVariable lv[] = lvt.getLocalVariableTable();
+ for (int i = 0; i < lv.length; i++) {
+ LocalVariable lvEntry = lv[i];
+ l.add(lvEntry);
+ }
+ Collections.sort(l, new MyComparator());
+ return l;
+ }
+
+ public String stringify(LocalVariableTable lvt, int slotIndex) {
+ LocalVariable lv[] = lvt.getLocalVariableTable();
+ LocalVariable lvEntry = lv[slotIndex];
+ StringBuffer sb = new StringBuffer();
+ sb.append(lvEntry.getSignature()).append(" ").append(lvEntry.getName()).append("(").append(lvEntry.getIndex())
+ .append(") start=").append(lvEntry.getStartPC()).append(" len=").append(lvEntry.getLength());
+ return sb.toString();
+ }
+
+ public String stringify(List<LocalVariable> l, int slotIndex) {
+ LocalVariable lvEntry = (LocalVariable) l.get(slotIndex);
+ StringBuffer sb = new StringBuffer();
+ sb.append(lvEntry.getSignature()).append(" ").append(lvEntry.getName()).append("(").append(lvEntry.getIndex())
+ .append(") start=").append(lvEntry.getStartPC()).append(" len=").append(lvEntry.getLength());
+ return sb.toString();
+ }
+
+ public String stringify(LocalVariableTable lvt) {
+ if (lvt == null) {
+ return "";
+ }
+ StringBuffer sb = new StringBuffer();
+ sb.append("LocalVariableTable. Entries=#" + lvt.getTableLength()).append("\n");
+ LocalVariable lv[] = lvt.getLocalVariableTable();
+ for (int i = 0; i < lv.length; i++) {
+ LocalVariable lvEntry = lv[i];
+ sb.append(lvEntry.getSignature()).append(" ").append(lvEntry.getName()).append("(").append(lvEntry.getIndex())
+ .append(") start=").append(lvEntry.getStartPC()).append(" len=").append(lvEntry.getLength()).append("\n");
+ }
+
+ return sb.toString();
+ }
+
+ public static class CountingFilenameFilter implements FilenameFilter {
+
+ private String suffix;
+ private int count;
+
+ public CountingFilenameFilter(String s) {
+ this.suffix = s;
+ }
+
+ public boolean accept(File dir, String name) {
+ if (name.endsWith(suffix)) {
+ count++;
+ }
+ return false;
+ }
+
+ public int getCount() {
+ return count;
+ }
+ }
+
+ public static class MyComparator implements Comparator<LocalVariable> {
+ public int compare(LocalVariable o1, LocalVariable o2) {
+ LocalVariable l1 = (LocalVariable) o1;
+ LocalVariable l2 = (LocalVariable) o2;
+ if (l1.getName().equals(l2.getName())) {
+ return l1.getStartPC() - l2.getStartPC();
+ } else {
+ return l1.getName().compareTo(l2.getName());
+ }
+ }
+
+ }
+
+ protected Method getMethodFromClass(JavaClass clazz, String methodName) {
+ Method[] meths = clazz.getMethods();
+ for (int i = 0; i < meths.length; i++) {
+ Method method = meths[i];
+ if (method.getName().equals(methodName)) {
+ return meths[i];
+ }
+ }
+ return null;
+ }
+
+ protected File getClassResource(String resourceName) {
+ return new File(getClass().getResource(resourceName).getFile());
+ }
+
+ protected Method findMethod(JavaClass jc, String string) {
+ for (Method m : jc.getMethods()) {
+ if (m.getName().equals(string)) {
+ return m;
+ }
+ }
+ return null;
+ }
+
+ protected ResolvedMember findMethod(ResolvedType outerType, String string) {
+ for (ResolvedMember method: outerType.getDeclaredMethods()) {
+ if (method.getName().equals(string)) {
+ return method;
+ }
+ }
+ return null;
+ }
+
+
+}
diff --git a/testing/src/test/java/org/aspectj/testing/XMLBasedAjcTestCaseForJava10OrLater.java b/testing/src/test/java/org/aspectj/testing/XMLBasedAjcTestCaseForJava10OrLater.java
new file mode 100644
index 000000000..4a8c63f11
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/XMLBasedAjcTestCaseForJava10OrLater.java
@@ -0,0 +1,31 @@
+/* *******************************************************************
+ * Copyright (c) 2018 Contributors
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Andy Clement
+ * ******************************************************************/
+package org.aspectj.testing;
+
+import org.aspectj.util.LangUtil;
+
+/**
+ * Ensure sure tests are running on the right level of JDK.
+ *
+ * @author Andy Clement
+ */
+public abstract class XMLBasedAjcTestCaseForJava10OrLater extends XMLBasedAjcTestCase {
+
+ @Override
+ public void runTest(String title) {
+ if (!LangUtil.is10VMOrGreater()) {
+ throw new IllegalStateException("These tests should be run on Java 10 or later");
+ }
+ super.runTest(title);
+ }
+
+}
diff --git a/testing/src/test/java/org/aspectj/testing/XMLBasedAjcTestCaseForJava11OrLater.java b/testing/src/test/java/org/aspectj/testing/XMLBasedAjcTestCaseForJava11OrLater.java
new file mode 100644
index 000000000..b71fc19e9
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/XMLBasedAjcTestCaseForJava11OrLater.java
@@ -0,0 +1,31 @@
+/* *******************************************************************
+ * Copyright (c) 2018 Contributors
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Andy Clement
+ * ******************************************************************/
+package org.aspectj.testing;
+
+/**
+ * Makes sure tests are running on the right level of JDK.
+ *
+ * @author Andy Clement
+ */
+public abstract class XMLBasedAjcTestCaseForJava11OrLater extends XMLBasedAjcTestCase {
+
+ @Override
+ public void runTest(String title) {
+ // Check we are on Java11
+ String property = System.getProperty("java.version");
+ if (!property.startsWith("11")) {
+ throw new IllegalStateException("These tests should be run on Java 11 or later");
+ }
+ super.runTest(title);
+ }
+
+}
diff --git a/testing/src/test/java/org/aspectj/testing/XMLBasedAjcTestCaseForJava9OrLater.java b/testing/src/test/java/org/aspectj/testing/XMLBasedAjcTestCaseForJava9OrLater.java
new file mode 100644
index 000000000..205b55273
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/XMLBasedAjcTestCaseForJava9OrLater.java
@@ -0,0 +1,32 @@
+/* *******************************************************************
+ * Copyright (c) 2018 Contributors
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Andy Clement
+ * ******************************************************************/
+package org.aspectj.testing;
+
+import org.aspectj.util.LangUtil;
+
+/**
+ * Makes sure tests are running on the right level of JDK.
+ *
+ * @author Andy Clement
+ */
+public abstract class XMLBasedAjcTestCaseForJava9OrLater extends XMLBasedAjcTestCase {
+
+ @Override
+ public void runTest(String title) {
+ // Check we are on Java9 or later
+ if (!LangUtil.is19VMOrGreater()) {
+ throw new IllegalStateException("These tests should be run on Java 9 or later");
+ }
+ super.runTest(title);
+ }
+
+}
diff --git a/testing/src/test/java/org/aspectj/testing/ajde/CompileCommand.java b/testing/src/test/java/org/aspectj/testing/ajde/CompileCommand.java
new file mode 100644
index 000000000..86c5fbaf7
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/ajde/CompileCommand.java
@@ -0,0 +1,439 @@
+/* *******************************************************************
+ * Copyright (c) 2003 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Wes Isberg initial implementation
+ * Helen Hawkins Converted to new interface (bug 148190)
+ * ******************************************************************/
+
+package org.aspectj.testing.ajde;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.aspectj.ajde.core.AjCompiler;
+import org.aspectj.ajde.core.IBuildMessageHandler;
+import org.aspectj.ajde.core.IBuildProgressMonitor;
+import org.aspectj.ajde.core.ICompilerConfiguration;
+import org.aspectj.ajde.core.IOutputLocationManager;
+import org.aspectj.ajde.core.JavaOptions;
+import org.aspectj.bridge.AbortException;
+import org.aspectj.bridge.ICommand;
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.IMessage.Kind;
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.bridge.MessageHandler;
+import org.aspectj.testing.harness.bridge.Globals;
+import org.aspectj.util.FileUtil;
+
+/**
+ * This re-uses the same config file to setup ajde so that recompiles appear to be of the same configuration.
+ *
+ * @since Java 1.3 (uses dynamic proxies)
+ */
+public class CompileCommand implements ICommand {
+ // time out waiting for build at three minutes
+ long MAX_TIME = 180 * 1000;
+ // this proxy ignores calls
+ InvocationHandler proxy = new VoidInvocationHandler();
+ InvocationHandler loggingProxy = new LoggingInvocationHandler();
+ MyMessageHandler myHandler = new MyMessageHandler();
+ long endTime;
+ boolean buildNextFresh;
+ File tempDir;
+
+ private AjCompiler compiler;
+
+ /**
+ * Clients call this before repeatCommand as a one-shot request for a full rebuild of the same configuration. (Requires a
+ * downcast from ICommand to CompileCommand.)
+ */
+ public void buildNextFresh() {
+ buildNextFresh = true;
+ }
+
+ // --------- ICommand interface
+ public boolean runCommand(String[] args, IMessageHandler handler) {
+ setup(args);
+ myHandler.start();
+ long startTime = System.currentTimeMillis();
+ try {
+ compiler.buildFresh();
+ } finally {
+ runCommandCleanup();
+ }
+ return !myHandler.hasError();
+ }
+
+ public boolean repeatCommand(IMessageHandler handler) {
+ myHandler.start();
+ long startTime = System.currentTimeMillis();
+ // System.err.println("recompiling...");
+ if (buildNextFresh) {
+ buildNextFresh = false;
+ compiler.buildFresh();
+ } else {
+ compiler.build();
+ }
+ return !myHandler.hasError();
+ }
+
+ void runCommandCleanup() {
+ if (null != tempDir) {
+ FileUtil.deleteContents(tempDir);
+ tempDir.delete();
+ }
+ }
+
+ // set by build progress monitor when done
+ void setEndTime(long endTime) {
+ this.endTime = endTime;
+ }
+
+ private void setup(String[] args) {
+ File config = writeConfig(args);
+ if (null == config) {
+ throw new Error("unable to write config file");
+ }
+ IBuildProgressMonitor buildProgressMonitor = new MyBuildProgressMonitor();
+ String classesDir = "../testing/bin/classes";
+ for (int i = 0; i < args.length; i++) {
+ if ("-d".equals(args[i]) && ((1 + i) < args.length)) {
+ classesDir = args[1 + i];
+ break;
+ }
+ }
+ MyCompilerConfig compilerConfig = new MyCompilerConfig();
+ compiler = new AjCompiler("blah", compilerConfig, buildProgressMonitor, myHandler);
+ }
+
+ private File writeConfig(String[] args) {
+ tempDir = FileUtil.getTempDir("CompileCommand");
+ File result = new File(tempDir, "config.lst");
+ OutputStream out = null;
+ try {
+ out = new FileOutputStream(result);
+ PrintStream outs = new PrintStream(out, true);
+ for (int i = 0; i < args.length; i++) {
+ outs.println(args[i]);
+ }
+ return result;
+ } catch (IOException e) {
+ return null;
+ } finally {
+ try {
+ out.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ // private Object makeLoggingProxy(Class interfac) {
+ // return Proxy.newProxyInstance(
+ // interfac.getClassLoader(),
+ // new Class[] { interfac },
+ // loggingProxy);
+ // }
+
+ private Object makeProxy(Class interfac) {
+ return Proxy.newProxyInstance(interfac.getClassLoader(), new Class[] { interfac }, proxy);
+ }
+}
+
+class MyMessageHandler implements IBuildMessageHandler {
+
+ boolean hasError;
+ boolean hasWarning;
+
+ private MessageHandler messageHandler = new MessageHandler(false);
+
+ public boolean handleMessage(IMessage message) throws AbortException {
+ maintainHasWarning(message.getKind());
+ return messageHandler.handleMessage(message);
+ }
+
+ private void maintainHasWarning(IMessage.Kind kind) {
+ if (!hasError) {
+ if (IMessage.ERROR.isSameOrLessThan(kind)) {
+ hasError = true;
+ hasWarning = true;
+ }
+ }
+ if (!hasWarning && IMessage.WARNING.isSameOrLessThan(kind)) {
+ hasWarning = true;
+ }
+ }
+
+ public boolean hasWarning() {
+ return hasWarning;
+ }
+
+ public boolean hasError() {
+ return hasError;
+ }
+
+ public void start() {
+ hasWarning = false;
+ hasError = false;
+ messageHandler.init(true);
+ }
+
+ public void dontIgnore(Kind kind) {
+ messageHandler.dontIgnore(kind);
+ }
+
+ public void ignore(Kind kind) {
+ messageHandler.ignore(kind);
+ }
+
+ public boolean isIgnoring(Kind kind) {
+ return messageHandler.isIgnoring(kind);
+ }
+
+}
+
+class MyBuildProgressMonitor implements IBuildProgressMonitor {
+
+ public void begin() {
+ }
+
+ public void finish(boolean wasFullBuild) {
+ }
+
+ public boolean isCancelRequested() {
+ return false;
+ }
+
+ public void setProgress(double percentDone) {
+ }
+
+ public void setProgressText(String text) {
+ }
+
+}
+
+class VoidInvocationHandler implements InvocationHandler {
+ public Object invoke(Object me, Method method, Object[] args) throws Throwable {
+ // System.err.println("Proxying"
+ // // don't call toString on self b/c proxied
+ // // + " me=" + me.getClass().getName()
+ // + " method=" + method
+ // + " args=" + (LangUtil.isEmpty(args)
+ // ? "[]" : Arrays.asList(args).toString()));
+ return null;
+ }
+}
+
+class LoggingInvocationHandler implements InvocationHandler {
+ public Object invoke(Object me, Method method, Object[] args) throws Throwable {
+ System.err.println("Proxying " + render(method, args));
+ return null;
+ }
+
+ public static String render(Class c) {
+ if (null == c) {
+ return "(Class) null";
+ }
+ String result = c.getName();
+ if (result.startsWith("java")) {
+ int loc = result.lastIndexOf(".");
+ if (-1 != loc) {
+ result = result.substring(loc + 1);
+ }
+ }
+ return result;
+ }
+
+ public static String render(Method method, Object[] args) {
+ StringBuffer sb = new StringBuffer();
+ sb.append(render(method.getReturnType()));
+ sb.append(" ");
+ sb.append(method.getName());
+ sb.append("(");
+ Class[] parmTypes = method.getParameterTypes();
+ int parmTypesLength = (null == parmTypes ? 0 : parmTypes.length);
+ int argsLength = (null == args ? 0 : args.length);
+ boolean doType = (parmTypesLength == argsLength);
+ for (int i = 0; i < argsLength; i++) {
+ if (i > 0) {
+ sb.append(", ");
+ }
+ if (doType) {
+ sb.append("(");
+ sb.append(render(parmTypes[i]));
+ sb.append(") ");
+ }
+ if (null == args[i]) {
+ sb.append("null");
+ } else { // also don't recurse into proxied toString?
+ sb.append(args[i].toString());
+ }
+ }
+ sb.append(")");
+ return sb.toString();
+ }
+}
+
+class MyCompilerConfig implements ICompilerConfiguration {
+
+ private Set inpath;
+ private Set aspectPath;
+ private String outJar;
+ private IOutputLocationManager locationMgr;
+
+ public Set getAspectPath() {
+ return aspectPath;
+ }
+
+ public void setAspectPath(Set path) {
+ aspectPath = path;
+ }
+
+ public String getClasspath() {
+ return Globals.S_aspectjrt_jar;
+ }
+
+ public Set getInpath() {
+ return inpath;
+ }
+
+ public void setInpath(Set input) {
+ inpath = input;
+ }
+
+ public Map getJavaOptionsMap() {
+ return JavaOptions.getDefaultJavaOptions();
+ }
+
+ public List getProjectXmlConfigFiles() {
+ return Collections.EMPTY_LIST;
+ }
+
+ public String getOutJar() {
+ return outJar;
+ }
+
+ public void configurationRead() {
+ }
+
+ public void setOutJar(String input) {
+ outJar = input;
+ }
+
+ public IOutputLocationManager getOutputLocationManager() {
+ if (locationMgr == null) {
+ locationMgr = new MyOutputLocationManager();
+ }
+ return locationMgr;
+ }
+
+ public String getNonStandardOptions() {
+ return null;
+ }
+
+ public List getProjectSourceFiles() {
+ return null;
+ }
+
+ public List getProjectSourceFilesChanged() {
+ return null;
+ }
+
+ public Map getSourcePathResources() {
+ return null;
+ }
+
+ public int getConfigurationChanges() {
+ return ICompilerConfiguration.EVERYTHING;
+ }
+
+ public List getClasspathElementsWithModifiedContents() {
+ return null;
+ }
+
+ public String getProjectEncoding() {
+ return null;
+ }
+
+ public String getProcessor() {
+ return null;
+ }
+
+ public String getProcessorPath() {
+ return null;
+ }
+
+ @Override
+ public String getModulepath() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public String getModuleSourcepath() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+}
+
+class MyOutputLocationManager implements IOutputLocationManager {
+
+ public List getAllOutputLocations() {
+ return null;
+ }
+
+ public File getDefaultOutputLocation() {
+ return null;
+ }
+
+ public File getOutputLocationForClass(File compilationUnit) {
+ return null;
+ }
+
+ public File getOutputLocationForResource(File resource) {
+ return null;
+ }
+
+ public String getUniqueIdentifier() {
+ return null;
+ }
+
+ public Map getInpathMap() {
+ return Collections.EMPTY_MAP;
+ }
+
+ public String getSourceFolderForFile(File sourceFile) {
+ return null;
+ }
+
+ public void reportFileWrite(String outputfile, int filetype) {
+ }
+
+ public void reportFileRemove(String outputfile, int filetype) {
+ }
+
+ public int discoverChangesSince(File dir, long buildtime) {
+ return 0;
+ }
+
+ public String getProjectEncoding() {
+ return null;
+ }
+
+}
diff --git a/testing/src/test/java/org/aspectj/testing/harness/bridge/AbstractRunSpec.java b/testing/src/test/java/org/aspectj/testing/harness/bridge/AbstractRunSpec.java
new file mode 100644
index 000000000..4e61a512c
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/harness/bridge/AbstractRunSpec.java
@@ -0,0 +1,918 @@
+/* *******************************************************************
+ * 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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * Wes Isberg 2004 updates
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+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;
+
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.bridge.IMessageHolder;
+import org.aspectj.bridge.ISourceLocation;
+import org.aspectj.bridge.Message;
+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.util.options.Option;
+import org.aspectj.testing.util.options.Option.InvalidInputException;
+import org.aspectj.testing.util.options.Options;
+import org.aspectj.testing.util.options.Values;
+import org.aspectj.testing.xml.IXmlWritable;
+import org.aspectj.testing.xml.SoftMessage;
+import org.aspectj.testing.xml.XMLWriter;
+import org.aspectj.util.LangUtil;
+
+/**
+ * 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 {
+
+ /** true if we expect to use a staging directory */
+ boolean isStaging;
+
+ /** true if this spec permits bad input (e.g., to test error handling) */
+ boolean badInput;
+
+ protected String description;
+
+ /** optional source location of the specification itself */
+ protected ISourceLocation sourceLocation;
+
+ private BitSet skipSet;
+ private boolean skipAll;
+
+ protected String xmlElementName; // nonfinal only for clone()
+ protected final ArrayList<String> keywords;
+ protected final IMessageHolder /* IMessage */messages;
+ protected final ArrayList<String> options;
+ protected final ArrayList<String> paths;
+ // XXXXXunused 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 boolean skipIfAnyChildSkipped; // nonfinal only for cloning
+
+ public AbstractRunSpec(String xmlElementName) {
+ this(xmlElementName, true);
+ }
+
+ public AbstractRunSpec(String xmlElementName, boolean skipIfAnyChildSkipped) {
+ if (null == xmlElementName) {
+ xmlElementName = "spec";
+ }
+ this.xmlElementName = xmlElementName;
+ messages = new MessageHandler(true);
+ options = new ArrayList<String>();
+ paths = new ArrayList<String>();
+ // XXXXXunused sourceLocations = new ArrayList();
+ keywords = new ArrayList<String>();
+ children = new ArrayList<IRunSpec>();
+ 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;
+ }
+
+ public void setBadInput(boolean badInput) {
+ this.badInput = badInput;
+ }
+
+ 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);
+ }
+
+ /** @return ((null == s) || (0 == s.trim().length())); */
+ public static boolean isEmptyTrimmed(String s) {
+ return ((null == s) || (0 == s.length()) || (0 == s.trim().length()));
+ }
+
+ /** Add keyword if non-empty and not duplicate */
+ public void addKeyword(String keyword) {
+ if (!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<String> getKeywordsList() {
+ return makeList(keywords);
+ }
+
+ // ------- options - String args
+
+ /** @return ArrayList of String options */
+ public ArrayList<String> 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]);
+ }
+ }
+ }
+
+ // --------------- (String) paths
+ /** @return ArrayList of String paths */
+ public ArrayList<String> getPathsList() {
+ return makeList(paths);
+ }
+
+ /** @return String[] of paths */
+ public String[] getPathsArray() {
+ return (String[]) paths.toArray(new String[0]);
+ }
+
+ 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]);
+ }
+ }
+ }
+
+ // --------------------- 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) {
+ if (!messages.handleMessage(message)) {
+ String s = "invalid message: " + message;
+ throw new IllegalArgumentException(s);
+ }
+ }
+ }
+
+ public void addMessage(String message) {
+ if (null != message) {
+ addMessage(BridgeUtil.readMessage(message));
+ }
+ }
+
+ /**
+ * 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 {
+ String m = "not message: " + o;
+ addMessage(new Message(m, IMessage.WARNING, null, null));
+ }
+ }
+ }
+ }
+
+ /** @return int number of message of this kind (optionally or greater */
+ public int numMessages(IMessage.Kind kind, boolean orGreater) {
+ return messages.numMessages(kind, orGreater);
+ }
+
+ public IMessageHolder getMessages() {
+ return messages;
+ }
+
+ 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<IRunSpec> getChildren() {
+ return makeList(children);
+ }
+
+ /** @return copy of children list without children to skip */
+ public ArrayList<IRunSpec> getWorkingChildren() {
+ if (skipAll) {
+ return new ArrayList<IRunSpec>();
+ }
+ if (null == skipSet) {
+ return getChildren();
+ }
+ ArrayList<IRunSpec> result = new ArrayList<IRunSpec>();
+ int i = 0;
+ for (Iterator<IRunSpec> iter = children.listIterator(); iter.hasNext(); i++) {
+ IRunSpec 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) {
+ AbstractRunSpec arsChild = (AbstractRunSpec) child;
+ if (!arsChild.adoptParentValues(runtime, handler)) {
+ skipSet.set(i);
+ if (!skipped) {
+ skipped = true;
+ if (skipIfAnyChildSkipped) { // no need to continue checking
+ skipAll = true;
+ 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 XMLWriter.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 = XMLWriter.flattenList(list);
+ // return XMLWriter.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, XMLWriter.flattenList(keywords));
+ }
+ if (!LangUtil.isEmpty(xmlNames.optionsName) && !LangUtil.isEmpty(options)) {
+ out.printAttribute(xmlNames.optionsName, XMLWriter.flattenList(options));
+ }
+ if (!LangUtil.isEmpty(xmlNames.pathsName) && !LangUtil.isEmpty(paths)) {
+ out.printAttribute(xmlNames.pathsName, XMLWriter.flattenList(paths));
+ }
+ if (!LangUtil.isEmpty(xmlNames.commentName) && !LangUtil.isEmpty(comment)) {
+ out.printAttribute(xmlNames.commentName, comment);
+ }
+ if (isStaging && !LangUtil.isEmpty(xmlNames.stagingName)) {
+ out.printAttribute(xmlNames.stagingName, "true");
+ }
+ if (badInput && !LangUtil.isEmpty(xmlNames.badInputName)) {
+ out.printAttribute(xmlNames.badInputName, "true");
+ }
+ }
+
+ /**
+ * 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 < messages.numMessages(null, true)) {
+ SoftMessage.writeXml(out, messages);
+ }
+
+ }
+
+ /**
+ * Write children. Assumes attributes are closed, can write child elements of current element.
+ */
+ protected void writeChildren(XMLWriter out) {
+ if (0 < children.size()) {
+ for (Iterator<IRunSpec> 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<IRunSpec> 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() {
+ StringBuffer result = new StringBuffer();
+ addListCount("options", options, result);
+ addListCount("paths", paths, result);
+ // XXXXXunused addListCount("sourceLocations", sourceLocations, result);
+ List<IMessage> messagesList = messages.getUnmodifiableListView();
+ addListCount("messages", messagesList, result);
+
+ return result.toString().trim();
+ }
+
+ public String toLongString() {
+ String mssg = "";
+ if (0 < messages.numMessages(null, true)) {
+ mssg = " expected messages (" + MessageUtil.renderCounts(messages) + ")";
+ }
+ return getPrintName() + containedToLongString() + mssg.trim();
+ }
+
+ /** @return String of the form (# [options|paths|locations|messages]).. */
+ protected String containedToLongString() {
+ StringBuffer result = new StringBuffer();
+ addListEntries("options", options, result);
+ addListEntries("paths", paths, result);
+ // XXXXXunused addListEntries("sourceLocations", sourceLocations, result);
+ List<IMessage> messagesList = messages.getUnmodifiableListView();
+ addListEntries("messages", messagesList, result);
+
+ return result.toString();
+ }
+
+ protected void initClone(AbstractRunSpec spec) throws CloneNotSupportedException {
+ /*
+ * clone associated objects only if not (used as?) read-only.
+ */
+ spec.badInput = badInput;
+ spec.children.clear();
+ for (Iterator<IRunSpec> iter = children.iterator(); iter.hasNext();) {
+ // clone these...
+ IRunSpec child = iter.next();
+ // require all child classes to support clone?
+ if (child instanceof AbstractRunSpec) {
+ spec.addChild((AbstractRunSpec) ((AbstractRunSpec) child).clone());
+ } else {
+ throw new Error("unable to clone " + child);
+ }
+ }
+ spec.comment = comment;
+ spec.description = description;
+ spec.dirChanges.clear();
+ spec.dirChanges.addAll(dirChanges);
+ spec.isStaging = spec.isStaging;
+ spec.keywords.clear();
+ spec.keywords.addAll(keywords);
+ spec.messages.clearMessages();
+ MessageUtil.handleAll(spec.messages, messages, false);
+ spec.options.clear();
+ spec.options.addAll(options);
+ spec.paths.clear();
+ spec.paths.addAll(paths);
+ spec.runtime.copy(runtime);
+ spec.skipAll = skipAll;
+ spec.skipIfAnyChildSkipped = skipIfAnyChildSkipped;
+ if (null != skipSet) {
+ spec.skipSet = new BitSet();
+ spec.skipSet.or(skipSet);
+ }
+ // spec.sourceLocation = sourceLocation;
+ // spec.sourceLocations.clear();
+ // XXXXXunused spec.sourceLocations.addAll(sourceLocations);
+ spec.xmlElementName = xmlElementName;
+ spec.xmlNames = ((AbstractRunSpec.XMLNames) xmlNames.clone());
+ }
+
+ private static void addListCount(String name, List<?> list, StringBuffer sink) {
+ int size = list.size();
+ if ((null != list) && (0 < size)) {
+ sink.append(" " + size + " ");
+ sink.append(name);
+ }
+ }
+
+ private static void addListEntries(String name, List<?> list, StringBuffer sink) {
+ if ((null != list) && (0 < list.size())) {
+ sink.append(" " + list.size() + " ");
+ sink.append(name);
+ sink.append(": ");
+ sink.append(list.toString());
+ }
+ }
+
+ private <T> ArrayList<T> makeList(List<T> list) {
+ ArrayList<T> result = new ArrayList<T>();
+ 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. XXX this really should be replaced with nested
+ * properties associated logical name with actual name (or placeholders for "unused" and "default").
+ */
+ public static class XMLNames {
+ public static final XMLNames DEFAULT = new XMLNames(null, "description", "sourceLocation", "keywords", "options", "paths",
+ "comment", "staging", "badInput", false, false, false);
+ final String descriptionName;
+ final String sourceLocationName;
+ final String keywordsName;
+ final String optionsName;
+ final String pathsName;
+ final String commentName;
+ final String stagingName;
+ final String badInputName;
+ final boolean skipDirChanges;
+ final boolean skipMessages;
+ final boolean skipChildren;
+
+ protected Object clone() {
+ return new XMLNames(null, descriptionName, sourceLocationName, keywordsName, optionsName, pathsName, commentName,
+ stagingName, badInputName, skipDirChanges, skipMessages, skipChildren);
+ }
+
+ // not runtime, skipAll, skipIfAnyChildSkipped, skipSet
+ // sourceLocations
+ /**
+ * 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, String stagingName, String badInputName, 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);
+ this.stagingName = (null != stagingName ? stagingName : defaultNames.stagingName);
+ this.badInputName = (null != badInputName ? badInputName : defaultNames.badInputName);
+ } else {
+ this.descriptionName = descriptionName;
+ this.sourceLocationName = sourceLocationName;
+ this.keywordsName = keywordsName;
+ this.optionsName = optionsName;
+ this.pathsName = pathsName;
+ this.commentName = commentName;
+ this.stagingName = stagingName;
+ this.badInputName = badInputName;
+ }
+ }
+ }
+
+ /** subclasses implement this to create and set up a run */
+ abstract public IRunIterator makeRunIterator(Sandbox sandbox, Validator validator);
+
+ /** 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<String>();
+ }
+
+ 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 accepted by validOptions, optionally removing the parent option.
+ *
+ * @param validOptions String[] of options to extract
+ * @param remove if true, then remove any parent option matched
+ * @return String[] containing any validOptions[i] in parentOptions
+ *
+ */
+ public Values extractOptions(Options validOptions, boolean remove, StringBuffer errors) {
+ Values result = Values.EMPTY;
+ if (null == errors) {
+ errors = new StringBuffer();
+ }
+ if (null == validOptions) {
+ errors.append("null options");
+ return result;
+ }
+ if (LangUtil.isEmpty(parentOptions)) {
+ return result;
+ }
+ // boolean haveOption = false;
+ String[] parents = (String[]) parentOptions.toArray(new String[0]);
+ try {
+ result = validOptions.acceptInput(parents);
+ } catch (InvalidInputException e) {
+ errors.append(e.getFullMessage());
+ return result;
+ }
+ if (remove) {
+ Option.Value[] values = result.asArray();
+ for (int i = 0; i < values.length; i++) {
+ Option.Value value = values[i];
+ if (null == value) {
+ continue;
+ }
+ final int max = i + value.option.numArguments();
+ if (max > i) {
+ if (max >= parents.length) {
+ errors.append("expecting more args for " + value.option + " at [" + i + "]: " + Arrays.asList(parents));
+ return result;
+ }
+ // XXX verify
+ for (int j = i; j < max; j++) {
+ parentOptions.remove(parents[j]);
+ }
+ i = max - 1;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * 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<String> result = new ArrayList<String>();
+ // boolean haveOption = false;
+ for (int i = 0; i < validOptions.length; i++) {
+ String option = validOptions[i];
+ if (LangUtil.isEmpty(option)) {
+ continue;
+ }
+ for (ListIterator<String> iter = parentOptions.listIterator(); iter.hasNext();) {
+ String parentOption = 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<String> 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/test/java/org/aspectj/testing/harness/bridge/AjcMessageHandler.java b/testing/src/test/java/org/aspectj/testing/harness/bridge/AjcMessageHandler.java
new file mode 100644
index 000000000..f4a88c3b2
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/harness/bridge/AjcMessageHandler.java
@@ -0,0 +1,343 @@
+/* *******************************************************************
+ * 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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import java.util.*;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+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.bridge.IMessage.Kind;
+import org.aspectj.testing.util.BridgeUtil;
+import org.aspectj.testing.util.Diffs;
+import org.aspectj.util.LangUtil;
+
+/**
+ * Handle messages during test and calculate differences
+ * between expected and actual messages.
+ */
+public class AjcMessageHandler extends MessageHandler {
+
+ /** Comparator for enclosed IMessage diffs */
+ public static final Comparator COMP_IMessage =
+ BridgeUtil.Comparators.MEDIUM_IMessage;
+
+ /** Comparator for enclosed File diffs */
+ public static final Comparator COMP_File = BridgeUtil.Comparators.WEAK_File;
+
+ /** unmodifiable list of IMessage messages of any type */
+ private final List expectedMessagesAsList;
+
+ /** IMessageHolder variant of expectedMessagesAsList */
+ private final IMessageHolder expectedMessages;
+
+ /** number of messages FAIL or worse */
+ private final int numExpectedFailed;
+
+ /** true if there were no error or worse messages expected */
+ private final boolean expectingCommandTrue;
+
+ /** unmodifiable list of File expected to be recompiled */
+ private final List expectedRecompiled;
+ // Unused now, but reinstate when supported
+
+ /** if true, ignore warnings when calculating diffs and passed() */
+ private final boolean ignoreWarnings;
+
+ /** list of File actually recompiled */
+ private List actualRecompiled;
+
+ /** cache expected/actual diffs, nullify if any new message */
+ private transient CompilerDiffs diffs;
+
+ AjcMessageHandler(IMessageHolder expectedMessages) {
+ this(expectedMessages, false);
+ }
+ /**
+ * @param messages the (constant) IMessageHolder with expected messages
+ */
+ AjcMessageHandler(
+ IMessageHolder expectedMessages,
+ boolean ignoreWarnings) {
+ LangUtil.throwIaxIfNull(messages, "messages");
+ this.expectedMessages = expectedMessages;
+ expectedMessagesAsList = expectedMessages.getUnmodifiableListView();
+ expectedRecompiled = Collections.EMPTY_LIST;
+ this.ignoreWarnings = ignoreWarnings;
+ int fails = 0;
+ int errors = 0;
+ for (Iterator iter = expectedMessagesAsList.iterator();
+ iter.hasNext();
+ ) {
+ IMessage m = (IMessage) iter.next();
+ IMessage.Kind kind = m.getKind();
+ if (IMessage.FAIL.isSameOrLessThan(kind)) {
+ fails++;
+ } else if (m.isError()) {
+ errors++;
+ }
+ }
+ expectingCommandTrue = (0 == (errors + fails));
+ numExpectedFailed = fails;
+ }
+
+ /** 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");
+ }
+ super.handleMessage(message);
+ return expecting(message);
+ }
+
+ /**
+ * 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);
+ }
+
+ /** Generate differences between expected and actual errors and warnings */
+ public CompilerDiffs getCompilerDiffs() {
+ if (null == diffs) {
+ final List<IMessage> expected;
+ final List<IMessage> actual;
+ if (!ignoreWarnings) {
+ expected = expectedMessages.getUnmodifiableListView();
+ actual = this.getUnmodifiableListView();
+ } else {
+ expected =
+ Arrays.asList(
+ expectedMessages.getMessages(IMessage.ERROR, true));
+ actual = Arrays.asList(this.getMessages(IMessage.ERROR, true));
+ }
+ // we ignore unexpected info messages,
+ // but we do test for expected ones
+ final Diffs messages;
+ boolean usingNew = true; // XXX extract old API's after shake-out period
+ if (usingNew) {
+ final IMessage.Kind[] NOSKIPS = new IMessage.Kind[0];
+ IMessage.Kind[] skipActual = new IMessage.Kind[] { IMessage.INFO };
+ int expectedInfo
+ = MessageUtil.numMessages(expected, IMessage.INFO, false);
+ if (0 < expectedInfo) {
+ // fyi, when expecting any info messages, have to expect all
+ skipActual = NOSKIPS;
+ }
+ messages = Diffs.makeDiffs(
+ "message",
+ (IMessage[]) expected.toArray(new IMessage[0]),
+ (IMessage[]) actual.toArray(new IMessage[0]),
+ NOSKIPS,
+ skipActual);
+ } else {
+ messages = Diffs.makeDiffs(
+ "message",
+ expected,
+ actual,
+ COMP_IMessage,
+ Diffs.ACCEPT_ALL,
+ CompilerDiffs.SKIP_UNEXPECTED_INFO);
+ }
+ Diffs recompiled =
+ Diffs.makeDiffs(
+ "recompiled",
+ expectedRecompiled,
+ actualRecompiled,
+ COMP_File);
+ diffs = new CompilerDiffs(messages, recompiled);
+ }
+ return diffs;
+ }
+
+ /**
+ * 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() {
+ return !getCompilerDiffs().different;
+ }
+
+ /** @return true if we are expecting the command to fail - i.e., any expected errors */
+ public boolean expectingCommandTrue() {
+ return expectingCommandTrue;
+ }
+
+ /**
+ * 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");
+ }
+ // Report all messages except expected fail+ messages,
+ // which will cause the reported-to handler client to gack.
+ // XXX need some verbose way to report even expected fail+
+ final boolean fastFail = false; // do all messages
+ if (0 == numExpectedFailed) {
+ MessageUtil.handleAll(handler, this, fastFail);
+ } else {
+ IMessage[] ra = getMessagesWithoutExpectedFails();
+ MessageUtil.handleAll(handler, ra, fastFail);
+ }
+
+ CompilerDiffs diffs = getCompilerDiffs();
+ if (diffs.different) {
+ diffs.messages.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 message ", EOL, diffs.messages.unexpected);
+ render(sb, " missing message ", EOL, diffs.messages.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
+ }
+
+ /**
+ * 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
+ */
+ private boolean expecting(IMessage message) {
+ boolean match = false;
+ if (null != message) {
+ for (Iterator iter = expectedMessagesAsList.iterator();
+ iter.hasNext();
+ ) {
+ // amc - we have to compare against all messages to consume multiple
+ // text matches on same line. Return true if any matches.
+ if (0 == COMP_IMessage.compare(message, iter.next())) {
+ match = true;
+ }
+ }
+ }
+ if (!match) {
+ diffs = null;
+ }
+ return match;
+ }
+
+ private IMessage[] getMessagesWithoutExpectedFails() {
+ IMessage[] result = super.getMessages(null, true);
+ // remove all expected fail+ (COSTLY)
+ ArrayList<IMessage> list = new ArrayList<>();
+ int leftToFilter = numExpectedFailed;
+ for (int i = 0; i < result.length; i++) {
+ if ((0 == leftToFilter)
+ || !IMessage.FAIL.isSameOrLessThan(result[i].getKind())) {
+ list.add(result[i]);
+ } else {
+ // see if this failure was expected
+ if (expectedMessagesHasMatchFor(result[i])) {
+ leftToFilter--; // ok, don't add
+ } else {
+ list.add(result[i]);
+ }
+ }
+ }
+ result = (IMessage[]) list.toArray(new IMessage[0]);
+ return result;
+ }
+
+ /**
+ * @param actual the actual IMessage to seek a match for in expected messages
+ * @return true if actual message is matched in the expected messages
+ */
+ private boolean expectedMessagesHasMatchFor(IMessage actual) {
+ for (Iterator iter = expectedMessagesAsList.iterator();
+ iter.hasNext();
+ ) {
+ IMessage expected = (IMessage) iter.next();
+ if (0 == COMP_IMessage.compare(expected, actual)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** @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 {
+ /** Skip info messages when reporting unexpected messages */
+ static final Diffs.Filter SKIP_UNEXPECTED_INFO = new Diffs.Filter() {
+ public boolean accept(Object o) {
+ return ((o instanceof IMessage) && !((IMessage) o).isInfo());
+ }
+ };
+ public final Diffs messages;
+ public final Diffs recompiled;
+ public final boolean different;
+
+ public CompilerDiffs(Diffs messages, Diffs recompiled) {
+ this.recompiled = recompiled;
+ this.messages = messages;
+ different = (messages.different || recompiled.different);
+ }
+ }
+}
diff --git a/testing/src/test/java/org/aspectj/testing/harness/bridge/AjcTest.java b/testing/src/test/java/org/aspectj/testing/harness/bridge/AjcTest.java
new file mode 100644
index 000000000..bdebae02c
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/harness/bridge/AjcTest.java
@@ -0,0 +1,586 @@
+/* *******************************************************************
+ * 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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import java.io.*;
+import java.io.File;
+import java.util.*;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+//import org.aspectj.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;
+
+/**
+ * An AjcTest has child subruns (compile, [inc-compile|run]*).
+ * XXX title keys shared between all instances
+ * (add Thread to key to restrict access?)
+ */
+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, e.g., as
+ * <pre>-ajctest[Require|Skip]Keywords=keyword{,keyword}..</pre>.
+ * See VALID_SUFFIXES for complete list.
+ */
+ 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 staging, skip badInput,
+ * 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 TITLE_LIST = "TitleList=";
+ private static final String TITLE_FAIL_LIST = "TitleFailList=";
+ private static final String TITLE_CONTAINS= "TitleContains=";
+ 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<String> VALID_SUFFIXES
+ = Collections.unmodifiableList(Arrays.asList(new String[]
+ { TITLE_LIST, TITLE_FAIL_LIST, TITLE_CONTAINS,
+ REQUIRE_KEYWORDS, SKIP_KEYWORDS, PICK_PR }));
+
+ /** Map String titlesName to List (String) of titles to accept */
+ private static final Map<String,List<String>> TITLES = new HashMap<String,List<String>>();
+
+ private static List<String> getTitles(String titlesName) {
+ return getTitles(titlesName, false);
+ }
+ private static List<String> getTitles(String titlesName, boolean fail) {
+ if (LangUtil.isEmpty(titlesName)) {
+ return Collections.emptyList();
+ }
+ List<String> result = (List<String>) TITLES.get(titlesName);
+ if (null == result) {
+ result = makeTitlesList(titlesName, fail);
+ TITLES.put(titlesName, result);
+ }
+ return result;
+ }
+
+ /**
+ * Make titles list per titlesKey, either a path to a file
+ * containing "[PASS|FAIL] {title}(..)" entries,
+ * or a comma-delimited list of titles.
+ * @param titlesKey a String, either a path to a file
+ * containing "[PASS|FAIL] {title}(..)" entries,
+ * or a comma-delimited list of titles.
+ * @param fail if true, only read titles prefixed "FAIL" from files
+ * @return the unmodifiable List of titles (maybe empty, never null)
+ */
+ private static List<String> makeTitlesList(String titlesKey, boolean fail) {
+ File file = new File(titlesKey);
+ return file.canRead()
+ ? readTitlesFile(file, fail)
+ : parseTitlesList(titlesKey);
+ }
+
+ /**
+ * Parse list of titles from comma-delmited list
+ * titlesList, trimming each entry and permitting
+ * comma to be escaped with '\'.
+ * @param titlesList a comma-delimited String of titles
+ * @return the unmodifiable List of titles (maybe empty, never null)
+ */
+ private static List<String> parseTitlesList(String titlesList) {
+ ArrayList<String> result = new ArrayList<String>();
+ String last = null;
+ StringTokenizer st = new StringTokenizer(titlesList, ",");
+ while (st.hasMoreTokens()) {
+ String next = st.nextToken().trim();
+ if (next.endsWith("\\")) {
+ next = next.substring(0, next.length()-1);
+ if (null == last) {
+ last = next;
+ } else {
+ last += next;
+ }
+ next = null;
+ } else if (null != last) {
+ next = (last + next).trim();
+ last = null;
+ } else {
+ next = next.trim();
+ }
+ if (!LangUtil.isEmpty(next)) {
+ result.add(next);
+ }
+ }
+ if (null != last) {
+ String m = "unterminated entry \"" + last; // XXX messages
+ System.err.println(m + "\" in " + titlesList);
+ result.add(last.trim());
+ }
+ return Collections.unmodifiableList(result);
+ }
+
+ /**
+ * Read titles from a test result file, accepting
+ * only those prefixed with [PASS|FAIL] and
+ * excluding the "[PASS|FAIL] Suite.Spec(.." entry.
+ * @param titlesFile the File containing a
+ * list of titles from test results,
+ * with some lines of the form
+ * <code>[PASS|FAIL] {title}()<code> (excluding
+ * <code>[PASS|FAIL] Suite.Spec(...<code>.
+ * @param titlesFile the File path to the file containing titles
+ * @param fail if true, only select titles prefixed "FAIL"
+ * @return the unmodifiable List of titles (maybe empty, never null)
+ */
+ private static List<String> readTitlesFile(File titlesFile, boolean fail) {
+ ArrayList<String> result = new ArrayList<String>();
+ Reader reader = null;
+ try {
+ reader = new FileReader(titlesFile);
+ BufferedReader lines = new BufferedReader(reader);
+ String line;
+ while (null != (line = lines.readLine())) {
+ if ((line.startsWith("FAIL ")
+ || (!fail && line.startsWith("PASS ")))
+ && (!line.substring(5).startsWith("Suite.Spec("))) {
+ String title = line.substring(5);
+ int loc = title.lastIndexOf("(");
+ if (-1 != loc) {
+ title = title.substring(0, loc);
+ }
+ result.add(title);
+ }
+ }
+ } catch (IOException e) {
+ System.err.println("ignoring titles in " + titlesFile); // XXX messages
+ e.printStackTrace(System.err);
+ } finally {
+ if (null != reader) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ return Collections.unmodifiableList(result);
+ }
+
+ /** 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);
+ }
+
+ protected void initClone(Spec spec)
+ throws CloneNotSupportedException {
+ super.initClone(spec);
+ spec.bugId = bugId;
+ spec.suiteDir = suiteDir;
+ spec.testDirOffset = testDirOffset;
+ }
+
+ public Object clone() throws CloneNotSupportedException {
+ Spec result = new Spec();
+ initClone(result);
+ return result;
+ }
+
+ public void setSuiteDir(File suiteDir) {
+ this.suiteDir = suiteDir;
+ }
+
+ public File getSuiteDir() {
+ return suiteDir;
+ }
+
+ /** @param bugId 100..999999 */
+ public void setBugId(int bugId) {
+ LangUtil.throwIaxIfFalse((bugId > 10) && (bugId < 1000000), "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 = XMLWriter.makeAttribute("dir", value);
+ if (0 != bugId) {
+ attr += " " + XMLWriter.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 a required keyword</li>
+ * <li>the spec does not have a required bugId</li>
+ * <li>the spec does not have a required title (description)n</li>
+ * </ul>
+ * When skipping, this issues a messages as to why skipped.
+ * Skip combinations are not guaranteed to work correctly. XXX
+ * @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;
+ List<String> permittedTitles = null;
+ List<String> permittedTitleStrings = null;
+ 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(TITLE_LIST)) {
+ option = option.substring(TITLE_LIST.length());
+ permittedTitles = getTitles(option);
+ } else if (option.startsWith(TITLE_FAIL_LIST)) {
+ option = option.substring(TITLE_FAIL_LIST.length());
+ permittedTitles = getTitles(option, true);
+ } else if (option.startsWith(TITLE_CONTAINS)) {
+ option = option.substring(TITLE_CONTAINS.length());
+ permittedTitleStrings = getTitles(option);
+ } 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 + "...)");
+ }
+ if (null != permittedTitleStrings) {
+ boolean gotHit = false;
+ for (Iterator<String> iter = permittedTitleStrings.iterator();
+ !gotHit && iter.hasNext();
+ ) {
+ String substring = (String) iter.next();
+ if (-1 != this.description.indexOf(substring)) {
+ gotHit = true;
+ }
+ }
+ if (!gotHit) {
+ String reason = "title "
+ + this.description
+ + " does not contain any of "
+ + option;
+ skipMessage(handler, reason);
+ return false;
+ }
+ } else if (null != permittedTitles) {
+ if (!permittedTitles.contains(this.description)) {
+ String reason = "titlesList "
+ + option
+ + " did not contain "
+ + this.description;
+ skipMessage(handler, reason);
+ return false;
+ }
+ } else {
+ // all other options handled as comma-delimited lists
+ List<String> specs = LangUtil.commaSplit(option);
+ // XXX also throw Error on empty specs...
+ for (Iterator<String> iter = specs.iterator(); iter.hasNext();) {
+ String spec = (String) iter.next();
+ if (null != havePr) {
+ if (havePr.equals(spec)) { // String.equals()
+ havePr = null;
+ }
+ } 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;
+
+ /**
+ * Count the number of AjcTest in this suite.
+ * @param spec
+ * @return
+ */
+ public static int countTests(Suite.Spec spec) {
+ return spec.children.size();
+ }
+
+ public static AjcTest.Spec[] getTests(Suite.Spec spec) {
+ if (null == spec) {
+ return new AjcTest.Spec[0];
+ }
+ return (AjcTest.Spec[]) spec.children.toArray(new AjcTest.Spec[0]);
+ }
+
+ 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 staging, skip badInput,
+ * 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
+ }
+
+ public Object clone() throws CloneNotSupportedException {
+ Spec spec = new Spec();
+ super.initClone(spec);
+ spec.suiteDir = suiteDir;
+ return spec;
+ }
+
+ /** @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;
+ }
+
+ /** @return 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/test/java/org/aspectj/testing/harness/bridge/CompilerRun.java b/testing/src/test/java/org/aspectj/testing/harness/bridge/CompilerRun.java
new file mode 100644
index 000000000..8d6c587fe
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/harness/bridge/CompilerRun.java
@@ -0,0 +1,1840 @@
+/* *******************************************************************
+ * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC),
+ * 2003 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * Wes Isberg 2003 updates
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import java.io.*;
+import java.lang.reflect.Array;
+import java.util.*;
+
+import org.aspectj.bridge.*;
+import org.aspectj.testing.ajde.CompileCommand;
+import org.aspectj.testing.run.*;
+import org.aspectj.testing.taskdefs.AjcTaskCompileCommand;
+import org.aspectj.testing.util.options.*;
+import org.aspectj.testing.util.options.Option.*;
+import org.aspectj.testing.xml.*;
+import org.aspectj.util.*;
+
+/**
+ * Run the compiler once.
+ * The lifecycle is as follows:
+ * <ul>
+ * <li>Spec (specification) is created.</li>
+ * <li>This is created using the Spec.</li>
+ * <li>setupAjcRun(Sandbox, Validator) is invoked,
+ * at which point this populates the shared sandbox
+ * with values derived from the spec and also
+ * sets up internal state based on both the sandbox
+ * and the spec.</li>
+ * <li>run(IRunStatus) is invoked, and this runs the compiler
+ * based on internal state, the spec, and the sandbox.</li>
+ * </ul>
+ * Programmer notes:
+ * <ul>
+ * <li>Paths are resolved absolutely, which fails to test the
+ * compiler's ability to find files relative to a source base</li>
+ * <li>This does not enforce the lifecycle.</li>
+ * <li>This must be used as the initial compile
+ * before doing an incremental compile.
+ * In that case, staging must be enabled.</li>
+ * </ul>
+ */
+public class CompilerRun implements IAjcRun {
+ // static final String JAVAC_COMPILER
+ // = JavacCompileCommand.class.getName();
+
+ 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 inpath option.
+ * The list is set up in setupAjcRun(..),
+ * which extracts only directories from the files attribute.
+ */
+ final List inpaths;
+
+ private CompilerRun(Spec spec) {
+ if (null == spec) {
+ throw new IllegalArgumentException("null spec");
+ }
+ this.spec = spec;
+ arguments = new ArrayList();
+ injars = new ArrayList();
+ inpaths = new ArrayList();
+ }
+
+
+ /**
+ * Select from input String[] if readable directories
+ * @param inputs String[] of input - null ignored
+ * @param baseDir the base directory of the input
+ * @return String[] of input that end with any input
+ */
+ public static String[] selectDirectories(String[] inputs, File baseDir) {
+ if (LangUtil.isEmpty(inputs)) {
+ return new String[0];
+ }
+ ArrayList result = new ArrayList();
+ for (int i = 0; i < inputs.length; i++) {
+ String input = inputs[i];
+ if (null == input) {
+ continue;
+ }
+ File inputFile = new File(baseDir, input);
+ if (inputFile.canRead() && inputFile.isDirectory()) {
+ result.add(input);
+ }
+ }
+ return (String[]) result.toArray(new String[0]);
+ }
+
+ /**
+ * Select from input String[] based on suffix-matching
+ * @param inputs String[] of input - null ignored
+ * @param suffixes String[] of suffix selectors - null ignored
+ * @param ignoreCase if true, ignore case
+ * @return String[] of input that end with any input
+ */
+ public static String[] endsWith(String[] inputs, String[] suffixes, boolean ignoreCase) {
+ if (LangUtil.isEmpty(inputs) || LangUtil.isEmpty(suffixes)) {
+ return new String[0];
+ }
+ if (ignoreCase) {
+ String[] temp = new String[suffixes.length];
+ for (int i = 0; i < temp.length; i++) {
+ String suff = suffixes[i];
+ temp[i] = (null == suff ? null : suff.toLowerCase());
+ }
+ suffixes = temp;
+ }
+ ArrayList result = new ArrayList();
+ for (int i = 0; i < inputs.length; i++) {
+ String input = inputs[i];
+ if (null == input) {
+ continue;
+ }
+ if (!ignoreCase) {
+ input = input.toLowerCase();
+ }
+ for (int j = 0; j < suffixes.length; j++) {
+ String suffix = suffixes[j];
+ if (null == suffix) {
+ continue;
+ }
+ if (input.endsWith(suffix)) {
+ result.add(input);
+ break;
+ }
+ }
+ }
+ return (String[]) result.toArray(new String[0]);
+ }
+
+ /**
+ * This checks that the spec is reasonable and does setup:
+ * <ul>
+ * <li>calculate and set sandbox testBaseSrcDir as {Sandbox.testBaseDir}/
+ * {Spec.testSrcDirOffset}/<li>
+ * <li>get the list of source File to compile as {Sandbox.testBaseSrcDir} /
+ * {Spec.getPaths..}</li>
+ * <li>get the list of extraClasspath entries to add to default classpath as
+ * {Sandbox.testBaseSrcDir} / {Spec.classpath..}</li>
+ * <li>get the list of aspectpath entries to use as the aspectpath as
+ * {Sandbox. testBaseSrcDir} / {Spec.aspectpath..}</li>
+ * </ul>
+ * All sources must be readable at this time,
+ * unless spec.badInput is true (for invalid-input tests).
+ * If staging, the source files and source roots are copied
+ * to a separate staging directory so they can be modified
+ * for incremental tests. Note that (as of this writing) the
+ * compiler only handles source roots for incremental tests.
+ * @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")) {
+ return false;
+ }
+
+ this.sandbox = sandbox;
+
+ String rdir = spec.testSrcDirOffset;
+ File testBaseSrcDir;
+ if ((null == rdir) || (0 == rdir.length())) {
+ testBaseSrcDir = sandbox.testBaseDir;
+ } else {
+ testBaseSrcDir = new File(sandbox.testBaseDir, rdir);
+ // XXX what if rdir is two levels deep?
+ if (!validator
+ .canReadDir(testBaseSrcDir, "sandbox.testBaseSrcDir")) {
+ return false;
+ }
+ }
+
+ // 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[] inpathPaths;
+ final String[] injarPaths;
+ final String[] srcPaths;
+ {
+ final String[] paths = spec.getPathsArray();
+ srcPaths =
+ endsWith(
+ paths,
+ CompilerRun.SOURCE_SUFFIXES,
+ true);
+ injarPaths =
+ endsWith(paths, CompilerRun.JAR_SUFFIXES, true);
+ inpathPaths =
+ selectDirectories(paths, testBaseSrcDir);
+ if (!spec.badInput) {
+ int found = inpathPaths.length + injarPaths.length + srcPaths.length;
+ if (paths.length != found) {
+ validator.fail("found " + found + " of " + paths.length + " sources");
+ }
+ }
+ }
+
+ // validate readable for sources
+ if (!spec.badInput) {
+ if (!validator.canRead(testBaseSrcDir, srcPaths, "sources")
+ // see validation of inpathPaths below due to ambiguous base dir
+ || !validator.canRead(
+ testBaseSrcDir,
+ spec.argfiles,
+ "argfiles")
+ || !validator.canRead(
+ testBaseSrcDir,
+ spec.classpath,
+ "classpath")
+ || !validator.canRead(
+ testBaseSrcDir,
+ spec.aspectpath,
+ "aspectpath")
+ || !validator.canRead(
+ testBaseSrcDir,
+ spec.sourceroots,
+ "sourceroots")
+ || !validator.canRead(
+ testBaseSrcDir,
+ spec.extdirs,
+ "extdirs")) {
+ return false;
+ }
+ }
+
+ int numSources =
+ srcPaths.length
+ + injarPaths.length
+ + inpathPaths.length
+ + spec.argfiles.length
+ + spec.sourceroots.length;
+ if (!spec.badInput && (numSources < 1)) {
+ validator.fail(
+ "no input jars, arg files, or source files or roots");
+ return false;
+ }
+
+ final File[] argFiles =
+ FileUtil.getBaseDirFiles(testBaseSrcDir, spec.argfiles);
+ final File[] injarFiles =
+ FileUtil.getBaseDirFiles(testBaseSrcDir, injarPaths);
+ final File[] inpathFiles =
+ FileUtil.getBaseDirFiles(testBaseSrcDir, inpathPaths);
+ final File[] aspectFiles =
+ FileUtil.getBaseDirFiles(testBaseSrcDir, spec.aspectpath);
+ final File[] extdirFiles =
+ FileUtil.getBaseDirFiles(testBaseSrcDir, spec.extdirs);
+ final File[] classFiles =
+ FileUtil.getBaseDirFiles(testBaseSrcDir, spec.classpath);
+ final File[] xlintFiles = (null == spec.xlintfile ? new File[0]
+ : FileUtil.getBaseDirFiles(testBaseSrcDir, new String[] {spec.xlintfile}));
+
+ // injars might be outjars in the classes dir...
+ for (int i = 0; i < injarFiles.length; i++) {
+ if (!injarFiles[i].exists()) {
+ injarFiles[i] = new File(sandbox.classesDir, injarPaths[i]);
+ }
+ }
+ for (int i = 0; i < inpathFiles.length; i++) {
+ if (!inpathFiles[i].exists()) {
+ inpathFiles[i] = new File(sandbox.classesDir, inpathPaths[i]);
+ }
+ }
+ // moved after solving any injars that were outjars
+ if (!validator.canRead(injarFiles, "injars")
+ || !validator.canRead(injarFiles, "injars")) {
+ return false;
+ }
+
+ // hmm - duplicates validation above, verifying getBaseDirFiles?
+ if (!spec.badInput) {
+ if (!validator.canRead(argFiles, "argFiles")
+ || !validator.canRead(injarFiles, "injarFiles")
+ || !validator.canRead(inpathFiles, "inpathFiles")
+ || !validator.canRead(aspectFiles, "aspectFiles")
+ || !validator.canRead(classFiles, "classFiles")
+ || !validator.canRead(xlintFiles, "xlintFiles")) {
+ return false;
+ }
+ }
+
+ final File[] srcFiles;
+ File[] sourcerootFiles = new File[0];
+ // source text files are copied when staging incremental tests
+ if (!spec.isStaging()) {
+ // XXX why this? was always? || (testBaseSrcDir != sandbox.stagingDir))) {
+ srcFiles =
+ FileUtil.getBaseDirFiles(
+ testBaseSrcDir,
+ srcPaths,
+ CompilerRun.SOURCE_SUFFIXES);
+ if (!LangUtil.isEmpty(spec.sourceroots)) {
+ sourcerootFiles =
+ FileUtil.getBaseDirFiles(
+ testBaseSrcDir,
+ spec.sourceroots,
+ null);
+ }
+ } else { // staging - copy files
+ if (spec.badInput) {
+ validator.info(
+ "badInput ignored - files checked when staging");
+ }
+ try {
+ // copy all files, then remove tagged ones
+ // XXX make copyFiles support a filter?
+ srcFiles =
+ FileUtil.copyFiles(
+ testBaseSrcDir,
+ srcPaths,
+ sandbox.stagingDir);
+ if (!LangUtil.isEmpty(spec.sourceroots)) {
+ sourcerootFiles =
+ FileUtil.copyFiles(
+ testBaseSrcDir,
+ spec.sourceroots,
+ sandbox.stagingDir);
+ // delete incremental files in sourceroot after copying // XXX inefficient
+ // an incremental file has an extra "." in name
+ // most .java files don't, because they are named after
+ // the principle type they contain, and simple type names
+ // have no dots.
+ FileFilter pickIncFiles = new FileFilter() {
+ public boolean accept(File file) {
+ if (file.isDirectory()) {
+ // continue recursion
+ return true;
+ }
+ String path = file.getPath();
+ // only source files are relevant to staging
+ if (!FileUtil.hasSourceSuffix(path)) {
+ return false;
+ }
+ int first = path.indexOf(".");
+ int last = path.lastIndexOf(".");
+ return (first != last);
+ }
+ };
+ for (int i = 0; i < sourcerootFiles.length; i++) {
+ FileUtil.deleteContents(
+ sourcerootFiles[i],
+ pickIncFiles,
+ false);
+ }
+ if (0 < sourcerootFiles.length) {
+ FileUtil.sleepPastFinalModifiedTime(
+ sourcerootFiles);
+ }
+ }
+ File[] files =
+ FileUtil.getBaseDirFiles(sandbox.stagingDir, srcPaths);
+ if (0 < files.length) {
+ FileUtil.sleepPastFinalModifiedTime(files);
+ }
+ } catch (IllegalArgumentException e) {
+ validator.fail("staging - bad input", e);
+ return false;
+ } catch (IOException e) {
+ validator.fail("staging - operations", e);
+ return false;
+ }
+ }
+ if (!spec.badInput
+ && !validator.canRead(srcFiles, "copied paths")) {
+ return false;
+ }
+ arguments.clear();
+
+ if (!LangUtil.isEmpty(xlintFiles)) {
+ arguments.add("-Xlintfile");
+ String sr = FileUtil.flatten(xlintFiles, null);
+ arguments.add(sr);
+ }
+ if (spec.outjar != null) {
+ arguments.add("-outjar");
+ arguments.add(new File(sandbox.classesDir,spec.outjar).getPath());
+ }
+ if (!LangUtil.isEmpty(extdirFiles)) {
+ arguments.add("-extdirs");
+ String sr = FileUtil.flatten(extdirFiles, null);
+ arguments.add(sr);
+ }
+ if (!LangUtil.isEmpty(sourcerootFiles)) {
+ arguments.add("-sourceroots");
+ String sr = FileUtil.flatten(sourcerootFiles, null);
+ arguments.add(sr);
+ }
+ if (!LangUtil.isEmpty(srcFiles)) {
+ arguments.addAll(Arrays.asList(FileUtil.getPaths(srcFiles)));
+ }
+ injars.clear();
+ if (!LangUtil.isEmpty(injarFiles)) {
+ injars.addAll(Arrays.asList(FileUtil.getPaths(injarFiles)));
+ }
+ inpaths.clear();
+ if (!LangUtil.isEmpty(inpathFiles)) {
+ inpaths.addAll(Arrays.asList(FileUtil.getPaths(inpathFiles)));
+ }
+ if (!LangUtil.isEmpty(argFiles)) {
+ String[] ra = FileUtil.getPaths(argFiles);
+ for (int j = 0; j < ra.length; j++) {
+ arguments.add("@" + ra[j]);
+ }
+ if (!spec.badInput && spec.isStaging) {
+ validator.fail(
+ "warning: files listed in argfiles not staged");
+ }
+ }
+
+ // save classpath and aspectpath in sandbox for this and other clients
+ final boolean checkReadable = !spec.badInput;
+ int size = spec.includeClassesDir ? 3 : 2;
+ File[] cp = new File[size + classFiles.length];
+ System.arraycopy(classFiles, 0, cp, 0, classFiles.length);
+ int index = classFiles.length;
+ if (spec.includeClassesDir) {
+ cp[index++] = sandbox.classesDir;
+ }
+ cp[index++] = Globals.F_aspectjrt_jar;
+ cp[index++] = Globals.F_testingclient_jar;
+ sandbox.compilerRunInit(this, testBaseSrcDir, aspectFiles,
+ checkReadable, cp, checkReadable, null);
+
+ // XXX todo set bootclasspath if set for forking?
+ return true;
+ }
+
+ /**
+ * 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#compiler}
+ * 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>
+ * @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 {
+ if (spec.outjar == null) {
+ 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);
+ }
+ String path = sandbox.classpathToString(this);
+ if (!LangUtil.isEmpty(path)) {
+ argList.add("-classpath");
+ argList.add(path);
+ }
+ path = sandbox.getBootclasspath(this);
+ if (!LangUtil.isEmpty(path)) {
+ argList.add("-bootclasspath");
+ argList.add(path);
+ }
+
+ path = sandbox.aspectpathToString(this);
+ if (!LangUtil.isEmpty(path)) {
+ argList.add("-aspectpath");
+ argList.add(path);
+ }
+
+ if (0 < injars.size()) {
+ argList.add("-injars");
+ argList.add(
+ FileUtil.flatten(
+ (String[]) injars.toArray(new String[0]),
+ null));
+ }
+
+ if (0 < inpaths.size()) {
+ argList.add("-inpath");
+ argList.add(
+ FileUtil.flatten(
+ (String[]) inpaths.toArray(new String[0]),
+ null));
+ }
+
+ // put specified arguments last, for better badInput tests
+ argList.addAll(setupResult.commandOptions);
+
+ // add both java/aspectj and argfiles
+ argList.addAll(arguments);
+
+ // XXX hack - seek 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 = spec.reuseCompiler
+ // throws IllegalStateException if null
+ ? sandbox.getCommand(this)
+ : ReflectionFactory.makeCommand(setupResult.compilerName, status);
+ DirChanges dirChanges = null;
+ if (null == compiler) {
+ MessageUtil.fail(
+ status,
+ "unable to make compiler " + setupResult.compilerName);
+ return false;
+ } else {
+ if (setupResult.compilerName != Spec.DEFAULT_COMPILER) {
+ MessageUtil.info(
+ status,
+ "compiler: " + setupResult.compilerName);
+ }
+ 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);
+ }
+ }
+ handlerResult = handler.passed();
+ 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 + ")";
+ }
+
+ /**
+ * 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";
+ public static final String DEFAULT_COMPILER =
+ ReflectionFactory.ECLIPSE;
+ static final String SEEK_PREFIX = "-seek:";
+ static final String SEEK_MESSAGE_PREFIX = "found: ";
+
+ private static final CRSOptions CRSOPTIONS = new CRSOptions();
+ /**
+ * Retitle description to title, paths to files, do comment,
+ * staging, badInput,
+ * do dirChanges, and print no chidren.
+ */
+ private static final AbstractRunSpec.XMLNames NAMES =
+ new AbstractRunSpec.XMLNames(
+ AbstractRunSpec.XMLNames.DEFAULT,
+ "title",
+ null,
+ null,
+ null,
+ "files",
+ null,
+ null,
+ null,
+ false,
+ false,
+ true);
+
+ /**
+ * If the source version warrants, add a -bootclasspath
+ * entry to the list of arguments to add.
+ * This will fail and return an error String if the
+ * required library is not found.
+ * @param sourceVersion the String (if any) describing the -source option
+ * (expecting one of [null, "1.3", "1.4", "1.5"].
+ * @param compilerName the String name of the target compiler
+ * @param toAdd the ArrayList to add -bootclasspath to
+ * @return the String describing any errors, or null if no errors
+ */
+ private static String updateBootclasspathForSourceVersion(
+ String sourceVersion,
+ String compilerName,
+ ArrayList toAdd) {
+ if (null == sourceVersion) {
+ return null;
+ }
+ if (3 != sourceVersion.length()) {
+ throw new IllegalArgumentException(
+ "bad version: " + sourceVersion);
+ }
+ if (null == toAdd) {
+ throw new IllegalArgumentException("null toAdd");
+ }
+ int version = sourceVersion.charAt(2) - '0';
+ switch (version) {
+ case (3) :
+ if (Globals.supportsJava("1.4")) {
+ if (!FileUtil.canReadFile(Globals.J2SE13_RTJAR)) {
+ return "no 1.3 libraries to handle -source 1.3";
+ }
+ toAdd.add("-bootclasspath");
+ toAdd.add(Globals.J2SE13_RTJAR.getAbsolutePath());
+ }
+ break;
+ case (4) :
+ if (!Globals.supportsJava("1.4")) {
+ if (ReflectionFactory
+ .ECLIPSE
+ .equals(compilerName)) {
+ return "run eclipse under 1.4 to handle -source 1.4";
+ }
+ if (!FileUtil.canReadFile(Globals.J2SE14_RTJAR)) {
+ return "no 1.4 libraries to handle -source 1.4";
+ }
+ toAdd.add("-bootclasspath");
+ toAdd.add(Globals.J2SE14_RTJAR.getAbsolutePath());
+ }
+ break;
+ case (5) :
+ return "1.5 not supported in CompilerRun";
+ case (0) :
+ // ignore - no version specified
+ break;
+ default :
+ throw new Error("unexpected version: " + version);
+ }
+ return null;
+ }
+
+ static CRSOptions testAccessToCRSOptions() {
+ return CRSOPTIONS;
+ }
+
+ static Options testAccessToOptions() {
+ return CRSOPTIONS.getOptions();
+ }
+
+ private static String[] copy(String[] input) {
+ if (null == input) {
+ return null;
+ }
+ String[] result = new String[input.length];
+ System.arraycopy(input, 0, result, 0, input.length);
+ return result;
+ }
+
+ protected String compiler;
+
+ // use same command - see also IncCompiler.Spec.fresh
+ protected boolean reuseCompiler;
+ protected boolean permitAnyCompiler;
+ protected boolean includeClassesDir;
+
+ protected TestSetup testSetup;
+
+ protected String[] argfiles = new String[0];
+ protected String[] aspectpath = new String[0];
+ protected String[] classpath = new String[0];
+ protected String[] sourceroots = new String[0];
+ protected String[] extdirs = new String[0];
+
+ /** src path = {suiteParentDir}/{testBaseDirOffset}/{testSrcDirOffset}/{path} */
+ protected String testSrcDirOffset;
+ protected String xlintfile;
+ protected String outjar;
+
+ public Spec() {
+ super(XMLNAME);
+ setXMLNames(NAMES);
+ compiler = DEFAULT_COMPILER;
+ }
+
+ protected void initClone(Spec spec)
+ throws CloneNotSupportedException {
+ super.initClone(spec);
+ spec.argfiles = copy(argfiles);
+ spec.aspectpath = copy(aspectpath);
+ spec.classpath = copy(classpath);
+ spec.compiler = compiler;
+ spec.includeClassesDir = includeClassesDir;
+ spec.reuseCompiler = reuseCompiler;
+ spec.permitAnyCompiler = permitAnyCompiler;
+ spec.sourceroots = copy(sourceroots);
+ spec.extdirs = copy(extdirs);
+ spec.outjar = outjar;
+ spec.testSetup = null;
+ if (null != testSetup) {
+ spec.testSetup = (TestSetup) testSetup.clone();
+ }
+ spec.testSrcDirOffset = testSrcDirOffset;
+ }
+
+ public Object clone() throws CloneNotSupportedException {
+ Spec result = new Spec();
+ initClone(result);
+ return result;
+ }
+
+ public void setIncludeClassesDir(boolean include) {
+ this.includeClassesDir = include;
+ }
+ public void setReuseCompiler(boolean reuse) {
+ this.reuseCompiler = reuse;
+ }
+
+ public void setPermitAnyCompiler(boolean permitAny) {
+ this.permitAnyCompiler = permitAny;
+ }
+
+ 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);
+ }
+
+ 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);
+ }
+
+ /**
+ * Add to default classpath
+ * (which includes aspectjrt.jar and testing-client.jar).
+ * @param files comma-delimited list of classpath entries - ignored if
+ * null or empty
+ */
+ public void setClasspath(String files) {
+ if (!LangUtil.isEmpty(files)) {
+ classpath = XMLWriter.unflattenList(files);
+ }
+ }
+
+ /**
+ * Set source roots, deleting any old ones
+ * @param files comma-delimited list of directories
+ * - ignored if null or empty
+ */
+ public void setSourceroots(String dirs) {
+ if (!LangUtil.isEmpty(dirs)) {
+ sourceroots = XMLWriter.unflattenList(dirs);
+ }
+ }
+ public void setXlintfile(String path) {
+ xlintfile = path;
+ }
+
+ public void setOutjar(String path) {
+ outjar = path;
+ }
+
+ /**
+ * Set extension dirs, deleting any old ones
+ * @param files comma-delimited list of directories
+ * - ignored if null or empty
+ */
+ public void setExtdirs(String dirs) {
+ if (!LangUtil.isEmpty(dirs)) {
+ extdirs = XMLWriter.unflattenList(dirs);
+ }
+ }
+
+ /**
+ * Set aspectpath, deleting any old ones
+ * @param files comma-delimited list of aspect jars - 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[]) copy(argfiles);
+ }
+
+ /**
+ * Make a copy of the array.
+ * @return an array with the same component type as source
+ * containing same elements, even if null.
+ * @throws IllegalArgumentException if source is null
+ */
+ public static final Object[] copy(Object[] source) {
+ LangUtil.throwIaxIfNull(source, "source");
+ final Class c = source.getClass().getComponentType();
+ Object[] result = (Object[]) Array.newInstance(c, source.length);
+ System.arraycopy(source, 0, result, 0, result.length);
+ return result;
+ }
+
+ /**
+ * 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>-source 1.4, 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 compilerClassName = compiler;
+ if (null != testSetup) {
+ compilerClassName = testSetup.compilerName;
+ }
+ if (null != compilerClassName) {
+ int loc = compilerClassName.lastIndexOf(".");
+ if (-1 != loc) {
+ compilerClassName =
+ compilerClassName.substring(loc + 1);
+ }
+ }
+ return compilerClassName;
+ }
+
+ /** @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;
+ }
+
+ protected String getPrintName() {
+ return "CompilerRun.Spec " + getShortCompilerName();
+ }
+
+ /**
+ * 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>
+ * <p>
+ * This also interprets any relevant System properties,
+ * e.g., from <code>JavaRun.BOOTCLASSPATH_KEY</code>.
+ * <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.
+ * This means the test is skipped.
+ * @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
+ final Spec spec = this;
+ final TestSetup result = new TestSetup();
+ result.compilerName = spec.compiler;
+ // urk - s.b. null, but expected
+ Values values = gatherValues(result);
+
+ if ((null == values) || (null != result.failureReason)) {
+ return checkResult(result);
+ }
+
+ // send info messages about
+ // forced staging when -incremental
+ // or staging but no -incremental flag
+ Option.Family getFamily =
+ CRSOPTIONS.crsIncrementalOption.getFamily();
+ final boolean haveIncrementalFlag =
+ (null != values.firstInFamily(getFamily));
+
+ if (spec.isStaging()) {
+ if (!haveIncrementalFlag) {
+ MessageUtil.info(
+ handler,
+ "staging but no -incremental flag");
+ }
+ } else if (haveIncrementalFlag) {
+ spec.setStaging(true);
+ MessageUtil.info(handler, "-incremental forcing staging");
+ }
+
+ if (hasInvalidOptions(values, result)) {
+ return checkResult(result);
+ }
+
+ // set compiler in result
+ getFamily = CRSOPTIONS.ajccompilerOption.getFamily();
+ Option.Value compiler = values.firstInFamily(getFamily);
+ if (null != compiler) {
+ result.compilerName
+ = CRSOPTIONS.compilerClassName(compiler.option);
+ if (null == result.compilerName) {
+ result.failureReason =
+ "unable to get class name for " + compiler;
+ return checkResult(result);
+ }
+ }
+ String compilerName =
+ (null == result.compilerName
+ ? spec.compiler
+ : result.compilerName);
+ // check compiler semantics
+ if (hasCompilerSpecErrors(compilerName, values, result)) {
+ return checkResult(result);
+ }
+ // add toadd and finish result
+ ArrayList args = new ArrayList();
+ String[] rendered = values.render();
+ if (!LangUtil.isEmpty(rendered)) {
+ args.addAll(Arrays.asList(rendered));
+ }
+ // update bootclasspath
+ getFamily = CRSOPTIONS.crsSourceOption.getFamily();
+ Option.Value source = values.firstInFamily(getFamily);
+ if (null != source) {
+ String sourceVersion = source.unflatten()[1];
+ ArrayList toAdd = new ArrayList();
+ /*String err =*/
+ updateBootclasspathForSourceVersion(
+ sourceVersion,
+ spec.compiler,
+ toAdd);
+ args.addAll(toAdd);
+ }
+ result.commandOptions = args;
+ result.result = true;
+ return checkResult(result);
+ }
+
+ /**
+ * Ensure exit invariant:
+ * <code>result.result == (null == result.failureReason)
+ * == (null != result.commandOptions)</code>
+ * @param result the TestSetup to verify
+ * @return result
+ * @throws Error if invariant is not true
+ */
+ TestSetup checkResult(TestSetup result) {
+ String err = null;
+ if (null == result) {
+ err = "null result";
+ } else if (result.result != (null == result.failureReason)) {
+ err =
+ result.result
+ ? "expected no failure: " + result.failureReason
+ : "fail for no reason";
+ } else if (result.result != (null != result.commandOptions)) {
+ err =
+ result.result
+ ? "expected command options"
+ : "unexpected command options";
+ }
+ if (null != err) {
+ throw new Error(err);
+ }
+ return result;
+ }
+
+ boolean hasInvalidOptions(Values values, TestSetup result) {
+ // not supporting 1.0 options any more
+ for (Iterator iter = CRSOPTIONS.invalidOptions.iterator();
+ iter.hasNext();
+ ) {
+ Option option = (Option) iter.next();
+ if (null != values.firstOption(option)) {
+ result.failureReason =
+ "invalid option in harness: " + option;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean hasCompilerSpecErrors(
+ String compilerName,
+ Values values,
+ TestSetup result) {
+ /*
+ * Describe any semantic conflicts between options.
+ * This skips:
+ * - old 1.0 options, including lenient v. strict
+ * - old ajc options, include !incremental and usejavac w/o javac
+ * - invalid eclipse options (mostly ajc)
+ * @param compilerName the String name of the target compiler
+ * @return a String describing any conflicts, or null if none
+ */
+ if (!permitAnyCompiler
+ && (!(ReflectionFactory.ECLIPSE.equals(compilerName)
+ || ReflectionFactory.OLD_AJC.equals(compilerName)
+ || CRSOptions.AJDE_COMPILER.equals(compilerName)
+ || CRSOptions.AJCTASK_COMPILER.equals(compilerName)
+ || permitAnyCompiler
+ ))) {
+ //|| BUILDER_COMPILER.equals(compilerName))
+ result.failureReason =
+ "unrecognized compiler: " + compilerName;
+ return true;
+ }
+ // not supporting ajc right now
+ if (null
+ != values.firstOption(CRSOPTIONS.ajccompilerOption)) {
+ result.failureReason = "ajc not supported";
+ return true;
+ }
+ // not supporting 1.0 options any more
+ for (Iterator iter = CRSOPTIONS.ajc10Options.iterator();
+ iter.hasNext();
+ ) {
+ Option option = (Option) iter.next();
+ if (null != values.firstOption(option)) {
+ result.failureReason = "old ajc 1.0 option: " + option;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected Values gatherValues(TestSetup result) {
+ final Spec spec = this;
+ // ---- local option values
+ final Values localValues;
+ final Options options = CRSOPTIONS.getOptions();
+ try {
+ String[] input = getOptionsArray();
+ // this handles reading options,
+ // flattening two-String options, etc.
+ localValues = options.acceptInput(input);
+ // all local values should be picked up
+ String err = Options.missedMatchError(input, localValues);
+ if (!LangUtil.isEmpty(err)) {
+ result.failureReason = err;
+ return null;
+ }
+ } catch (InvalidInputException e) {
+ result.failureReason = e.getFullMessage();
+ return null;
+ }
+
+ // ---- global option values
+ StringBuffer errs = new StringBuffer();
+ final Values globalValues =
+ spec.runtime.extractOptions(options, true, errs);
+ if (errs.length() > 0) {
+ result.failureReason = errs.toString();
+ return null;
+ }
+ final Values combined =
+ Values.wrapValues(
+ new Values[] { localValues, globalValues });
+
+ String err = combined.resolve();
+ if (null != err) {
+ result.failureReason = err;
+ return null;
+ }
+
+ return handleTestArgs(combined, result);
+ }
+
+ // final int len = globalValues.length() + localValues.length();
+ // final Option.Value[] combinedValues = new Option.Value[len];
+ // System.arraycopy(
+ // globalValues,
+ // 0,
+ // combinedValues,
+ // 0,
+ // globalValues.length());
+ // System.arraycopy(
+ // localValues,
+ // 0,
+ // combinedValues,
+ // globalValues.length(),
+ // localValues.length());
+ //
+ // result.compilerName = spec.compiler;
+ // if (0 < combinedValues.length) {
+ // // this handles option forcing, etc.
+ // String err = Options.resolve(combinedValues);
+ // if (null != err) {
+ // result.failureReason = err;
+ // return null;
+ // }
+ // if (!handleTestArgs(combinedValues, result)) {
+ // return null;
+ // }
+ // }
+ // return Values.wrapValues(combinedValues);
+ // }
+ /**
+ * This interprets and nullifies values for the test.
+ * @param values the Option.Value[] being processed
+ * @param result the TestSetup to modify
+ * @return false if error (caller should return), true otherwise
+ */
+ Values handleTestArgs(Values values, final TestSetup result) {
+ final Option.Family compilerFamily =
+ CRSOPTIONS.ajccompilerOption.getFamily();
+ Values.Selector selector = new Values.Selector() {
+ protected boolean accept(Option.Value value) {
+ if (null == value) {
+ return false;
+ }
+ Option option = value.option;
+ if (compilerFamily.sameFamily(option.getFamily())) {
+ if (value.prefix.isSet()) {
+ String compilerClass
+ = CRSOPTIONS.compilerClassName(option);
+ if (null == compilerClass) {
+ result.failureReason =
+ "unrecognized compiler: " + value;
+ throw Values.Selector.STOP;
+ }
+ if (!CRSOPTIONS.compilerIsLoadable(option)) {
+ result.failureReason =
+ "unable to load compiler: " + option;
+ throw Values.Selector.STOP;
+ }
+ result.compilerName = compilerClass;
+ }
+ return true;
+ } else if (
+ CRSOPTIONS.crsIgnoreWarnings.sameOptionIdentifier(
+ option)) {
+ result.ignoreWarnings = value.prefix.isSet();
+ result.ignoreWarningsSet = true;
+ return true;
+ }
+ return false;
+ }
+ };
+ return values.nullify(selector);
+ }
+
+ // /**
+ // * This interprets and nullifies values for the test.
+ // * @param values the Option.Value[] being processed
+ // * @param result the TestSetup to modify
+ // * @return false if error (caller should return), true otherwise
+ // */
+ // boolean handleTestArgs(Option.Value[] values, TestSetup result) {
+ // if (!LangUtil.isEmpty(values)) {
+ // for (int i = 0; i < values.length; i++) {
+ // Option.Value value = values[i];
+ // if (null == value) {
+ // continue;
+ // }
+ // Option option = value.option;
+ // if (option.sameOptionFamily(ECLIPSE_OPTION)) {
+ // if (!value.prefix.isSet()) {
+ // values[i] = null;
+ // continue;
+ // }
+ // String compilerClass =
+ // (String) COMPILER_OPTION_TO_CLASSNAME.get(
+ // option);
+ // if (null == compilerClass) {
+ // result.failureReason =
+ // "unrecognized compiler: " + value;
+ // return false;
+ // }
+ // result.compilerName = compilerClass;
+ // values[i] = null;
+ // } else if (
+ // option.sameOptionFamily(crsIgnoreWarnings)) {
+ // result.ignoreWarnings = value.prefix.isSet();
+ // result.ignoreWarningsSet = true;
+ // values[i] = null;
+ // }
+ // }
+ // }
+ // return true;
+ // }
+
+ // 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) {
+ out.startElement(xmlElementName, false);
+ if (!LangUtil.isEmpty(testSrcDirOffset)) {
+ out.printAttribute("dir", testSrcDirOffset);
+ }
+ super.writeAttributes(out);
+ if (!DEFAULT_COMPILER.equals(compiler)) {
+ out.printAttribute("compiler", compiler);
+ }
+ if (reuseCompiler) {
+ out.printAttribute("reuseCompiler", "true");
+ }
+// test-only feature
+// if (permitAnyCompiler) {
+// out.printAttribute("permitAnyCompiler", "true");
+// }
+ if (includeClassesDir) {
+ out.printAttribute("includeClassesDir", "true");
+ }
+ if (!LangUtil.isEmpty(argfiles)) {
+ out.printAttribute(
+ "argfiles",
+ XMLWriter.flattenFiles(argfiles));
+ }
+ if (!LangUtil.isEmpty(aspectpath)) {
+ out.printAttribute(
+ "aspectpath",
+ XMLWriter.flattenFiles(aspectpath));
+ }
+ if (!LangUtil.isEmpty(sourceroots)) {
+ out.printAttribute(
+ "sourceroots",
+ XMLWriter.flattenFiles(sourceroots));
+ }
+ if (!LangUtil.isEmpty(extdirs)) {
+ out.printAttribute(
+ "extdirs",
+ XMLWriter.flattenFiles(extdirs));
+ }
+ out.endAttributes();
+ if (!LangUtil.isEmpty(dirChanges)) {
+ DirChanges.Spec.writeXml(out, dirChanges);
+ }
+ SoftMessage.writeXml(out, getMessages());
+ 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 Object clone() {
+ TestSetup testSetup = new TestSetup();
+ testSetup.compilerName = compilerName;
+ testSetup.ignoreWarnings = ignoreWarnings;
+ testSetup.ignoreWarningsSet = ignoreWarningsSet;
+ testSetup.result = result;
+ testSetup.failureReason = failureReason;
+ testSetup.seek = seek;
+ if (null != commandOptions) {
+ testSetup.commandOptions = new ArrayList();
+ testSetup.commandOptions.addAll(commandOptions);
+ }
+ return testSetup;
+ }
+ public String toString() {
+ return "TestSetup("
+ + (null == compilerName ? "" : compilerName + " ")
+ + (!ignoreWarningsSet
+ ? ""
+ : (ignoreWarnings ? "" : "do not ")
+ + "ignore warnings ")
+ + (result ? "" : "setup failed")
+ + ")";
+ }
+ }
+
+ /**
+ * Options-related stuff in the spec.
+ */
+ static class CRSOptions {
+ // static final String BUILDER_COMPILER =
+ // "org.aspectj.ajdt.internal.core.builder.Builder.Command";
+ static final String AJDE_COMPILER =
+ CompileCommand.class.getName();
+
+ static final String AJCTASK_COMPILER =
+ AjcTaskCompileCommand.class.getName();
+
+ private final Map compilerOptionToLoadable = new TreeMap();
+ /*
+ * The options field in a compiler test permits some arbitrary
+ * command-line options to be set. It does not permit things
+ * like classpath, aspectpath, files, etc. which are set
+ * using other fields in the test specification, so the options
+ * permitted are a subset of those permitted on the command-line.
+ *
+ * Global options specified on the harness command-line are
+ * adopted for the compiler command-line if they are permitted
+ * in the options field. That means we have to detect each
+ * permitted option, rather than just letting all through
+ * for the compiler.
+ *
+ * Conversely, some options are targeted not at the compiler,
+ * but at the test itself (e.g., to ignore warnings, or to
+ * select a compiler.
+ *
+ * The harness can run many compilers, and they differ in
+ * which options are permitted. You can specify a compiler
+ * as an option (e.g., -eclipse). So the set of constraints
+ * on the list of permitted options can differ from test to test.
+ *
+ * The following code sets up the lists of permitted options
+ * and breaks out subsets for different compiler-variant checks.
+ * Most options are created but not named, but some options
+ * are named to detect corresponding values for further
+ * processing. e.g., the compiler options are saved so
+ * we can do per-compiler option verification.
+ *
+ */
+ private final Options crsOptions;
+ private final Family compilerFamily;
+ private final Option crsIncrementalOption;
+ private final Option crsSourceOption;
+ // these are options handled/absorbed by CompilerRun
+ private final Option crsIgnoreWarnings;
+ private final Option eclipseOption;
+ private final Option buildercompilerOption;
+ private final Option ajdecompilerOption;
+ private final Option javacOption;
+ private final Option ajctaskcompilerOption;
+ private final Option ajccompilerOption;
+ private final Map compilerOptionToClassname;
+ private final Set compilerOptions;
+ // compiler verification - permit but flag ajc 1.0 options
+ private final List ajc10Options;
+ private final List invalidOptions;
+
+ private CRSOptions() {
+ crsOptions = new Options(true);
+ Option.Factory factory = new Option.Factory("CompilerRun");
+ // compiler options go in map
+ eclipseOption =
+ factory.create(
+ "eclipse",
+ "compiler",
+ Option.FORCE_PREFIXES,
+ false);
+ compilerFamily = eclipseOption.getFamily();
+ buildercompilerOption =
+ factory.create(
+ "builderCompiler",
+ "compiler",
+ Option.FORCE_PREFIXES,
+ false);
+ ajctaskcompilerOption =
+ factory.create(
+ "ajctaskCompiler",
+ "compiler",
+ Option.FORCE_PREFIXES,
+ false);
+ ajdecompilerOption =
+ factory.create(
+ "ajdeCompiler",
+ "compiler",
+ Option.FORCE_PREFIXES,
+ false);
+ ajccompilerOption =
+ factory.create(
+ "ajc",
+ "compiler",
+ Option.FORCE_PREFIXES,
+ false);
+ javacOption =
+ factory.create(
+ "javac",
+ "compiler",
+ Option.FORCE_PREFIXES,
+ false);
+
+ Map map = new TreeMap();
+ map.put(eclipseOption, ReflectionFactory.ECLIPSE);
+ //map.put(BUILDERCOMPILER_OPTION, BUILDER_COMPILER);
+ map.put(
+ ajctaskcompilerOption,
+ AJCTASK_COMPILER);
+ map.put(ajdecompilerOption, AJDE_COMPILER);
+ map.put(ajccompilerOption, ReflectionFactory.OLD_AJC);
+ //map.put(JAVAC_OPTION, "XXX javac option not supported");
+ compilerOptionToClassname =
+ Collections.unmodifiableMap(map);
+
+ compilerOptions =
+ Collections.unmodifiableSet(
+ compilerOptionToClassname.keySet());
+ // options not permitted in the harness
+ List list = new ArrayList();
+ list.add(factory.create("workingdir"));
+ list.add(factory.create("argfile"));
+ list.add(factory.create("sourceroots"));
+ list.add(factory.create("outjar"));
+ invalidOptions = Collections.unmodifiableList(list);
+
+ // other options added directly
+ crsIncrementalOption = factory.create("incremental");
+
+ crsIgnoreWarnings = factory.create("ignoreWarnings");
+
+ crsSourceOption =
+ factory
+ .create(
+ "source",
+ "source",
+ Option.FORCE_PREFIXES,
+ false,
+ new String[][] { new String[] { "1.3", "1.4", "1.5" }
+ });
+
+ // ajc 1.0 options
+ // workingdir above in invalid options
+ list = new ArrayList();
+ list.add(factory.create("usejavac"));
+ list.add(factory.create("preprocess"));
+ list.add(factory.create("nocomment"));
+ list.add(factory.create("porting"));
+ list.add(factory.create("XOcodeSize"));
+ list.add(factory.create("XTargetNearSource"));
+ list.add(factory.create("XaddSafePrefix"));
+ list.add(
+ factory.create(
+ "lenient",
+ "lenient",
+ Option.FORCE_PREFIXES,
+ false));
+ list.add(
+ factory.create(
+ "strict",
+ "lenient",
+ Option.FORCE_PREFIXES,
+ false));
+ ajc10Options = Collections.unmodifiableList(list);
+
+ // -warn:.. and -g/-g:.. are not exclusive
+ if (!(factory.setupFamily("debug", true)
+ && factory.setupFamily("warning", true))) {
+ System.err.println("CompilerRun debug/warning fail!");
+ }
+ Option[] options =
+ new Option[] {
+ crsIncrementalOption,
+ crsIgnoreWarnings,
+ crsSourceOption,
+ factory.create(
+ "Xlint",
+ "XLint",
+ Option.FORCE_PREFIXES,
+ true),
+ factory.create("verbose"),
+ factory.create("emacssym"),
+ factory.create("referenceInfo"),
+ factory.create("nowarn"),
+ factory.create("deprecation"),
+ factory.create("noImportError"),
+ factory.create("proceedOnError"),
+ factory.create("preserveAllLocals"),
+ factory.create(
+ "warn",
+ "warning",
+ Option.STANDARD_PREFIXES,
+ true),
+ factory.create(
+ "g",
+ "debug",
+ Option.STANDARD_PREFIXES,
+ false),
+ factory.create(
+ "g:",
+ "debug",
+ Option.STANDARD_PREFIXES,
+ true),
+ factory.create(
+ "1.3",
+ "compliance",
+ Option.FORCE_PREFIXES,
+ false),
+ factory.create(
+ "1.4",
+ "compliance",
+ Option.FORCE_PREFIXES,
+ false),
+ factory.create(
+ "1.5",
+ "compliance",
+ Option.FORCE_PREFIXES,
+ false),
+ factory
+ .create(
+ "target",
+ "target",
+ Option.FORCE_PREFIXES,
+ false,
+ new String[][] { new String[] {
+ "1.1",
+ "1.2",
+ "1.3",
+ "1.4",
+ "1.5" }}),
+ factory.create("XnoInline"),
+ factory.create("XterminateAfterCompilation"),
+ factory.create("Xreweavable"),
+ factory.create("XnotReweavable"),
+ factory.create("XserializableAspects")
+ };
+
+ // among options not permitted: extdirs...
+
+ for (int i = 0; i < options.length; i++) {
+ crsOptions.addOption(options[i]);
+ }
+ for (Iterator iter = compilerOptions.iterator();
+ iter.hasNext();
+ ) {
+ crsOptions.addOption((Option) iter.next());
+ }
+ // these are recognized but records with them are skipped
+ for (Iterator iter = ajc10Options.iterator();
+ iter.hasNext();
+ ) {
+ crsOptions.addOption((Option) iter.next());
+ }
+ crsOptions.freeze();
+ }
+
+ Options getOptions() {
+ return crsOptions;
+ }
+
+ /**
+ * @return unmodifiable Set of options sharing the
+ * family "compiler".
+ */
+ Set compilerOptions() {
+ return compilerOptions;
+ }
+
+ /**
+ * @param option the compiler Option to get name for
+ * @return null if option is null or not a compiler option,
+ * or the fully-qualified classname of the ICommand
+ * implementing the compiler.
+ */
+ String compilerClassName(Option option) {
+ if ((null == option)
+ || (!compilerFamily.sameFamily(option.getFamily()))) {
+ return null;
+ }
+ return (String) compilerOptionToClassname.get(option);
+ }
+
+ /**
+ * Check that the compiler class associated with a compiler
+ * option can be loaded. This check only happens once;
+ * the result is cached (by compilerOption key) for later calls.
+ * @param compilerOption the Option (family compiler) to check
+ * @return true if compiler class for this option can be loaded
+ */
+ boolean compilerIsLoadable(Option compilerOption) {
+ LangUtil.throwIaxIfNull(compilerOption, "compilerName");
+ synchronized (compilerOptionToLoadable) {
+ Boolean result =
+ (Boolean) compilerOptionToLoadable.get(
+ compilerOption);
+ if (null == result) {
+ MessageHandler sink = new MessageHandler();
+ String compilerClassname =
+ (String) compilerOptionToClassname.get(
+ compilerOption);
+ if (null == compilerClassname) {
+ result = Boolean.FALSE;
+ } else {
+ ICommand c =
+ ReflectionFactory.makeCommand(
+ compilerClassname,
+ sink);
+
+ if ((null == c)
+ || sink.hasAnyMessage(
+ IMessage.ERROR,
+ true)) {
+ result = Boolean.FALSE;
+ } else {
+ result = Boolean.TRUE;
+ }
+ }
+ compilerOptionToLoadable.put(
+ compilerOption,
+ result);
+ }
+ return result.booleanValue();
+ }
+ }
+ } // CompilerRun.Spec.CRSOptions
+ } // CompilerRun.Spec
+} // CompilerRun \ No newline at end of file
diff --git a/testing/src/test/java/org/aspectj/testing/harness/bridge/DirChanges.java b/testing/src/test/java/org/aspectj/testing/harness/bridge/DirChanges.java
new file mode 100644
index 000000000..13281607b
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/harness/bridge/DirChanges.java
@@ -0,0 +1,614 @@
+/* *******************************************************************
+ * 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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+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;
+
+/**
+ * Calculate changes in a directory tree.
+ * This implements two different specification styles:
+ * <ul>
+ * <li>Specify files added, removed, updated, and/or a component
+ * to check any existing files</li>
+ * <li>Specify expected directory. When complete this checks that
+ * any files in expected directory are matched in the actual.
+ * (.class files are dissassembled before comparison.)
+ * </li>
+ * </ul>
+ * 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>
+ * When comparing directories, this ignores any paths containing "CVS".
+ */
+public class DirChanges {
+
+ public static final String DELAY_NAME = "dir-changes.delay";
+ private static final long DELAY;
+ static {
+ long delay = 10l;
+ try {
+ delay = Long.getLong(DELAY_NAME).longValue();
+ if ((delay > 40000) || (delay < 0)) {
+ delay = 10l;
+ }
+ } catch (Throwable t) {
+ // ignore
+ }
+ DELAY = delay;
+ }
+
+ 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
+ * specified in added/updated lists.
+ * This does not affect expected-directory comparison.
+ */
+ 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 added file to exist", !EXISTS, spec.added, doCompare);
+ result &= exists("at start, expected unchanged file to exist", EXISTS, spec.unchanged, doCompare);
+ result &= exists("at start, expected updated file to exist", EXISTS, spec.updated, doCompare);
+ result &= exists("at start, expected removed file to exist", EXISTS, spec.removed, doCompare);
+ startTime = System.currentTimeMillis();
+ // ensure tests don't complete in < 1 second, otherwise can confuse fast machines.
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ 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.
+ * This sleeps before checking until at least DELAY milliseconds after start.
+ * @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 long targetTime = startTime + spec.delayInMilliseconds;
+ do {
+ long curTime = System.currentTimeMillis();
+ if (curTime >= targetTime) {
+ break;
+ }
+ try {
+ Thread.sleep(targetTime-curTime);
+ } catch (InterruptedException e) {
+ break;
+ }
+ } while (true);
+ final IMessageHandler oldHandler = this.handler;
+ this.handler = handler;
+ try {
+ // variant 1: check specified files
+ // deferring comparison to end...
+ 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) {
+// // XXX validate that unchanged mod-time did not change
+// }
+ // variant 1: compare expected directory
+ if (result || !fastFail) {
+ result &= compareDir(srcBaseDir);
+ }
+ return result;
+ } finally {
+ this.handler = oldHandler;
+ baseDir = null;
+ startTime = 0l;
+ }
+ }
+
+ /**
+ * 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.expDir) {
+ return true;
+ }
+ File expDir = new File(srcBaseDir, spec.expDir);
+ File actDir = baseDir;
+ //System.err.println("XXX comparing actDir=" + actDir + " expDir=" + expDir);
+ 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.expDir))
+// ? null
+// : new File(baseDir, spec.expDir));
+ 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);
+ }
+// File-comparison code with a bit more generality -- too unweildy
+// /**
+// * 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.
+ * Currently the directory tree
+ * only is used to verify files that are expected
+ * and found after the process completes.
+ */
+ public static class Spec implements IXmlWritable {
+ /** XML element name */
+ public static final String XMLNAME = "dir-changes";
+
+ /** a symbolic name for the base directory */
+ String dirToken; // XXX default to class?
+
+ /** if set, then append to specified paths when seeking files */
+ String defaultSuffix;
+
+ /** relative path of dir with expected files for comparison */
+ String expDir;
+
+ long delayInMilliseconds = DELAY;
+
+ /** if true, fail on first mis-match */
+ boolean fastFail;
+
+ /** relative paths (String) of expected files added */
+ final ArrayList<String> added;
+
+ /** relative paths (String) of expected files removed/deleted */
+ final ArrayList<String> removed;
+
+ /** relative paths (String) of expected files updated/changed */
+ final ArrayList<String> updated;
+
+ /** relative paths (String) of expected files NOT
+ * added, removed, or changed
+ * XXX unchanged unimplemented
+ */
+ final ArrayList<String> unchanged;
+
+ 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.
+ * @param expectedDirRelativePath path relative to the test base
+ * of the directory containing expected results for the output dir.
+ */
+ public void setExpDir(String expectedDirRelativePath) {
+ expDir = expectedDirRelativePath;
+ }
+
+ public void setDelay(String delay) {
+ if (null != delay) {
+ // let NumberFormatException propogate up
+ delayInMilliseconds = Long.parseLong(delay);
+ if (delayInMilliseconds < 0l) {
+ delayInMilliseconds = 0l;
+ }
+ }
+ }
+
+ /**
+ * @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;
+ }
+
+ /** @return true if some list was specified */
+ private boolean hasFileList() {
+ return (!LangUtil.isEmpty(added)
+ || !LangUtil.isEmpty(removed)
+ || !LangUtil.isEmpty(updated)
+ || !LangUtil.isEmpty(unchanged)
+ );
+ }
+
+ /**
+ * Emit specification in XML form if not empty.
+ * This writes nothing if there is no expected dir
+ * and there are no added, removed, or changed.
+ * fastFail is written only if true, since the default is false.
+ */
+ public void writeXml(XMLWriter out) {
+ if (!hasFileList() && LangUtil.isEmpty(expDir)) {
+ return;
+ }
+ // XXX need to permit defaults here...
+ out.startElement(XMLNAME, false);
+ if (!LangUtil.isEmpty(dirToken)) {
+ out.printAttribute("dirToken", dirToken.trim());
+ }
+ if (!LangUtil.isEmpty(defaultSuffix)) {
+ out.printAttribute("defaultSuffix", defaultSuffix.trim());
+ }
+ if (!LangUtil.isEmpty(expDir)) {
+ out.printAttribute("expDir", expDir.trim());
+ }
+ 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));
+ }
+ if (fastFail) {
+ out.printAttribute("fastFail", "true");
+ }
+ 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.Spec> dirChanges) {
+ if (LangUtil.isEmpty(dirChanges)) {
+ return;
+ }
+ LangUtil.throwIaxIfNull(out, "out");
+ for (Iterator<DirChanges.Spec> iter = dirChanges.iterator(); iter.hasNext();) {
+ DirChanges.Spec spec = iter.next();
+ if (null == spec) {
+ continue;
+ }
+ spec.writeXml(out);
+ }
+ }
+
+} // class Spec
+
+}
diff --git a/testing/src/test/java/org/aspectj/testing/harness/bridge/FlatSuiteReader.java b/testing/src/test/java/org/aspectj/testing/harness/bridge/FlatSuiteReader.java
new file mode 100644
index 000000000..8d75d5620
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/harness/bridge/FlatSuiteReader.java
@@ -0,0 +1,376 @@
+/* *******************************************************************
+ * 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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.aspectj.bridge.AbortException;
+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.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.testing.util.UtilLineReader;
+import org.aspectj.util.FileUtil;
+import org.aspectj.util.LangUtil;
+
+/**
+ * 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(UtilLineReader)
+ */
+ public Object make(final UtilLineReader 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<Message> 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, UtilLineReader 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<String> newOptions = new ArrayList<>();
+ ArrayList<String> optionsCopy = result.getOptionsList();
+ for (String option: optionsCopy) {
+ 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<Message> makeMessages(// XXX weak - also support expected exceptions, etc.
+ Kind kind, String[] words, int start, File lastFile) {
+ ArrayList<Message> 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.<Message>emptyList() : 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 = MessageUtil.fail("reading " + suiteFile, e);
+ throw new AbortException(m);
+ }
+
+ return result;
+ }
+}
diff --git a/testing/src/test/java/org/aspectj/testing/harness/bridge/Globals.java b/testing/src/test/java/org/aspectj/testing/harness/bridge/Globals.java
new file mode 100644
index 000000000..ad62c9735
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/harness/bridge/Globals.java
@@ -0,0 +1,162 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC)
+ * 2003 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * Wes Isberg removed unused globals.
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.aspectj.util.FileUtil;
+import org.aspectj.util.LangUtil;
+
+/**
+ */
+public class Globals {
+
+ /** map from String version to String class implemented in that version or later */
+ private static final Map VM_CLASSES;
+
+ public static final String FORK_NAME = "harness.fork";
+ // XXX in testing-drivers/../package.htm
+ /** 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";
+ public static final String J2SE13_RTJAR_NAME = "j2se13.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;
+ // XXX used only by 1.0 compiler tests - deprecate?
+ public static final File J2SE13_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 File F_testingclient_jar =
+ new File(S_testingclient_jar);
+ public static final File F_aspectjrt_jar = new File(S_aspectjrt_jar);
+ public static final boolean globalsValid;
+
+ static {
+ J2SE13_RTJAR =
+ getRtJarFor(J2SE13_RTJAR_NAME, "c:/home/apps/jdk13");
+ J2SE14_RTJAR =
+ getRtJarFor(J2SE14_RTJAR_NAME, "c:/home/apps/jdk14");
+
+// String forkSpec = getSystemProperty(FORK_NAME, null);
+ globalsValid =
+ (FileUtil.canReadFile(F_testingclient_jar)
+ && FileUtil.canReadFile(F_aspectjrt_jar)
+ && FileUtil.canReadFile(J2SE13_RTJAR)
+ && FileUtil.canReadFile(J2SE14_RTJAR));
+ HashMap map = new HashMap();
+ map.put("1.2", "java.lang.ref.Reference");
+ map.put("1.3", "java.lang.reflect.Proxy");
+ map.put("1.4", "java.nio.Buffer");
+ map.put("1.5", "java.lang.annotation.Annotation");
+
+ VM_CLASSES = Collections.unmodifiableMap(map);
+ }
+
+ private static File getRtJarFor(
+ String propertyName,
+ String defaultLocation) {
+ File j2seJar = null;
+ try {
+ String path = getSystemProperty(propertyName, defaultLocation);
+ File file = new File(path);
+ if (file.exists()) {
+ File rtjar = null;
+ if (path.endsWith("rt.jar")) {
+ rtjar = file;
+ } else if (file.isDirectory()) {
+ path = file.getAbsolutePath() + "/jre/lib/rt.jar";
+ rtjar = new File(path);
+ }
+ if (rtjar.canRead() && rtjar.isFile()) {
+ j2seJar = rtjar;
+ }
+ }
+ } catch (Throwable t) { // avoid at all costs during static init
+ try {
+ t.printStackTrace(System.err);
+ } catch (Throwable x) {
+ // unable to log, better to just fail...
+ }
+ }
+ return j2seJar;
+ }
+
+ /**
+ *
+ * @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.
+ */
+ 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;
+ }
+
+
+ /**
+ * Detect whether Java version is supported.
+ * @param version String "1.2" or "1.3" or "1.4"
+ * @return true if the currently-running VM supports the version
+ * @throws IllegalArgumentException if version is not known
+ */
+ public static final boolean supportsJava(String version) {
+ LangUtil.throwIaxIfNull(version, "version");
+ String className = (String) VM_CLASSES.get(version);
+ if (null == className) {
+ throw new IllegalArgumentException("unknown version: " + version);
+ }
+ try {
+ Class.forName(className);
+ return true;
+ } catch (Throwable t) {
+ return false;
+ }
+ }
+
+}
diff --git a/testing/src/test/java/org/aspectj/testing/harness/bridge/IAjcRun.java b/testing/src/test/java/org/aspectj/testing/harness/bridge/IAjcRun.java
new file mode 100644
index 000000000..e685df5dd
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/harness/bridge/IRunSpec.java b/testing/src/test/java/org/aspectj/testing/harness/bridge/IRunSpec.java
new file mode 100644
index 000000000..dc35b493d
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/harness/bridge/IncCompilerRun.java b/testing/src/test/java/org/aspectj/testing/harness/bridge/IncCompilerRun.java
new file mode 100644
index 000000000..7c7f2c54e
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/harness/bridge/IncCompilerRun.java
@@ -0,0 +1,503 @@
+/* *******************************************************************
+ * 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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.aspectj.bridge.AbortException;
+import org.aspectj.bridge.ICommand;
+import org.aspectj.bridge.MessageUtil;
+import org.aspectj.testing.ajde.CompileCommand;
+import org.aspectj.testing.run.IRunIterator;
+import org.aspectj.testing.run.IRunStatus;
+import org.aspectj.testing.run.WrappedRunIterator;
+import org.aspectj.testing.util.StructureModelUtil;
+import org.aspectj.testing.util.StructureModelUtil.ModelIncorrectException;
+import org.aspectj.testing.xml.AjcSpecXmlReader;
+import org.aspectj.testing.xml.IXmlWritable;
+import org.aspectj.testing.xml.SoftMessage;
+import org.aspectj.testing.xml.XMLWriter;
+import org.aspectj.util.FileUtil;
+import org.aspectj.util.LangUtil;
+
+/**
+ * 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
+ * (1) tag is "same", or
+ * (2) 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) {
+ if ("same".equals(spec.tag)) {
+ return true;
+ }
+ boolean result = false;
+ try {
+ 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
+ // sigh - delay until after last last-mod-time
+ intHolder holder = new intHolder();
+ List copied = new ArrayList();
+ doStaging(validator,".java",holder,copied);
+ doStaging(validator,".jar",holder,copied);
+ doStaging(validator,".class",holder,copied);
+ doStaging(validator,".properties",holder,copied); // arbitrary resource extension
+ doStaging(validator,".xml",holder,copied); // arbitrary resource extension
+ if ((0 == holder.numCopies) && (0 == holder.numDeletes)) {
+ validator.fail("no files changed??");
+ } else {
+ result = (0 == holder.numFails);
+ }
+ if (0 < copied.size()) {
+ File[] files = (File[]) copied.toArray(new File[0]);
+ FileUtil.sleepPastFinalModifiedTime(files);
+ }
+ } catch (NullPointerException npe) {
+ validator.fail("staging - input", npe);
+ } catch (IOException e) {
+ validator.fail("staging - operations", e);
+ }
+ return result;
+ }
+
+
+ private void doStaging(final Validator validator, final String toSuffix,
+ final intHolder holder,final List copied)
+ throws IOException
+ {
+ final String fromSuffix = "." + spec.tag + toSuffix;
+ final String clip = ".delete" + toSuffix;
+ FileFilter deleteOrCount = new FileFilter() {
+ /** 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++;
+ validator.info("copying file: " + path);
+ copied.add(file);
+ } else {
+ doCopy = false;
+ path = path.substring(0, path.length()-clip.length()) + toSuffix;
+ File toDelete = new File(path);
+ if (toDelete.delete()) {
+ validator.info("deleted file: " + path);
+ 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);
+ }
+
+ private static class intHolder {
+ int numCopies;
+ int numDeletes;
+ int numFails;
+ }
+
+ /**
+ * @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
+ }
+ }
+ AjcMessageHandler handler = new AjcMessageHandler(spec.getMessages());
+ boolean handlerResult = false;
+ boolean commandResult = false;
+ boolean result = false;
+ boolean report = false;
+ try {
+ handler.init();
+ if (spec.fresh) {
+ if (compiler instanceof CompileCommand) { // urk
+ ((CompileCommand) compiler).buildNextFresh();
+ } else {
+ MessageUtil.info(handler, "fresh not supported by compiler: " + compiler);
+ }
+ }
+// final long startTime = System.currentTimeMillis();
+ commandResult = compiler.repeatCommand(handler);
+ if (!spec.checkModel.equals("")) {
+ StructureModelUtil.checkModel(spec.checkModel);
+ }
+ // 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);
+ }
+ }
+ } catch (ModelIncorrectException e) {
+ MessageUtil.fail(status,e.getMessage());
+ } 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
+ }
+
+ /**
+ * initializer/factory for IncCompilerRun.
+ */
+ public static class Spec extends AbstractRunSpec {
+ public static final String XMLNAME = "inc-compile";
+
+ protected boolean fresh;
+ protected ArrayList classesAdded;
+ protected ArrayList classesRemoved;
+ protected ArrayList classesUpdated;
+
+ protected String checkModel;
+
+ /**
+ * skip description, skip sourceLocation,
+ * do keywords, skip options, do paths as classes, do comment,
+ * skip staging (always true), skip badInput (irrelevant)
+ * 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();
+ checkModel="";
+ }
+
+ protected void initClone(Spec spec)
+ throws CloneNotSupportedException {
+ super.initClone(spec);
+ spec.fresh = fresh;
+ spec.tag = tag;
+ spec.classesAdded.clear();
+ spec.classesAdded.addAll(classesAdded);
+ spec.classesRemoved.clear();
+ spec.classesRemoved.addAll(classesRemoved);
+ spec.classesUpdated.clear();
+ spec.classesUpdated.addAll(classesUpdated);
+ }
+
+ public Object clone() throws CloneNotSupportedException {
+ Spec result = new Spec();
+ initClone(result);
+ return result;
+ }
+
+
+ public void setFresh(boolean fresh) {
+ this.fresh = fresh;
+ }
+
+ public void setTag(String input) {
+ tag = input;
+ }
+
+ public void setCheckModel(String thingsToCheck) {
+ this.checkModel=thingsToCheck;
+ }
+
+ public String toString() {
+ return "IncCompile.Spec(" + tag + ", " + super.toString() + ",["+checkModel+"])";
+ }
+
+ /** 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 = XMLWriter.makeAttribute("tag", tag);
+ out.startElement(xmlElementName, attr, false);
+ if (fresh) {
+ out.printAttribute("fresh", "true");
+ }
+ super.writeAttributes(out);
+ out.endAttributes();
+ if (!LangUtil.isEmpty(dirChanges)) {
+ DirChanges.Spec.writeXml(out, dirChanges);
+ }
+ SoftMessage.writeXml(out, getMessages());
+ 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/test/java/org/aspectj/testing/harness/bridge/JavaRun.java b/testing/src/test/java/org/aspectj/testing/harness/bridge/JavaRun.java
new file mode 100644
index 000000000..aade35f48
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/harness/bridge/JavaRun.java
@@ -0,0 +1,899 @@
+/* *******************************************************************
+ * 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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import org.aspectj.bridge.AbortException;
+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.util.TestUtil;
+import org.aspectj.testing.xml.SoftMessage;
+import org.aspectj.testing.xml.XMLWriter;
+import org.aspectj.util.FileUtil;
+import org.aspectj.util.LangUtil;
+import org.aspectj.weaver.loadtime.WeavingURLClassLoader;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.net.*;
+import java.security.Permission;
+import java.util.*;
+
+/**
+ * Run a class in this VM using reflection.
+ * Forked mode supported, but through system properties:
+ * - javarun.fork: anything to enable forking
+ * - javarun.java: path to java executable (optional)
+ * - javarun.java.home: JAVA_HOME for java (optional)
+ * (normally requires javarun.java)
+ * - javarun.classpath: a prefix to the run classpath (optional)
+ */
+public class JavaRun implements IAjcRun {
+
+ private static void appendClasspath(StringBuffer cp, Object[] entries) {
+ if (!LangUtil.isEmpty(entries)) {
+ for (int i = 0; i < entries.length; i++) {
+ Object entry = entries[i];
+ if (entry instanceof String) {
+ cp.append((String) entry);
+ cp.append(File.pathSeparator);
+ } else if (entry instanceof File) {
+ String s = FileUtil.getBestPath((File) entry);
+ if (null != s) {
+ cp.append(s);
+ cp.append(File.pathSeparator);
+ }
+ }
+ }
+ }
+ }
+
+ 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,
+ * and, if fork is enabled, that java can be read.
+ * @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;
+ sandbox.javaRunInit(this);
+ 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, true), "classpath dirs")
+ && (!spec.forkSpec.fork
+ || validator.canRead(spec.forkSpec.java, "java"))
+ );
+
+ }
+
+ /** caller must record any exceptions */
+ public boolean run(IRunStatus status)
+ throws IllegalAccessException,
+ InvocationTargetException,
+ ClassNotFoundException,
+ NoSuchMethodException {
+ boolean completedNormally = false;
+ boolean passed = false;
+ 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);
+ boolean includeClassesDir = true;
+ File[] dirs = sandbox.getClasspathDirectories(readable, this, includeClassesDir);
+ completedNormally = (spec.forkSpec.fork)
+ ? runInOtherVM(status, libs, dirs)
+ : runInSameVM(status, libs, dirs);
+ passed = completedNormally;
+ } finally {
+ if (!passed || !status.runResult()) {
+ MessageUtil.info(status, spec.toLongString());
+ MessageUtil.info(status, "sandbox: " + sandbox);
+ }
+ }
+ return passed;
+ }
+ protected boolean runInSameVM(IRunStatus status, File[] libs, File[] dirs) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+ ClassLoader loader = null;
+ boolean completedNormally = false;
+ boolean passed = false;
+ ByteArrayOutputStream outSnoop = null;
+ PrintStream oldOut = null;
+ ByteArrayOutputStream errSnoop = null;
+ PrintStream oldErr = null;
+ if (spec.outStreamIsError) {
+ outSnoop = new ByteArrayOutputStream();
+ oldOut = System.out;
+ System.setOut(new PrintStream(outSnoop, true));
+ }
+ if (spec.errStreamIsError) {
+ errSnoop = new ByteArrayOutputStream();
+ oldErr = System.err;
+ System.setErr(new PrintStream(errSnoop, true));
+ }
+ Class targetClass = null;
+ try {
+ final URL[] clAndLibs;
+ {
+ File[] files = sandbox.findFiles(spec.classpath);
+ URL[] clURLs = FileUtil.getFileURLs(files);
+ URL[] libURLs = FileUtil.getFileURLs(libs);
+ clAndLibs = new URL[clURLs.length + libURLs.length];
+ System.arraycopy(clURLs, 0, clAndLibs , 0, clURLs.length);
+ System.arraycopy(libURLs, 0, clAndLibs, clURLs.length, libURLs.length);
+ }
+ if (!spec.isLTW()) {
+ loader = new TestClassLoader(clAndLibs, dirs);
+ } else {
+ final URL[] aspectURLs;
+ {
+ File[] files = sandbox.findFiles(spec.aspectpath);
+ aspectURLs = FileUtil.getFileURLs(files);
+ }
+ ArrayList classpath = new ArrayList();
+ classpath.addAll(Arrays.asList(aspectURLs));
+ final URL[] classURLs;
+ {
+ classpath.addAll(Arrays.asList(clAndLibs));
+ URL[] urls = FileUtil.getFileURLs(dirs);
+ classpath.addAll(Arrays.asList(urls));
+ classpath.add(FileUtil.getFileURL(Globals.F_aspectjrt_jar));
+ classpath.add(FileUtil.getFileURL(Globals.F_testingclient_jar));
+ classURLs = (URL[]) classpath.toArray(new URL[0]);
+ }
+
+ ClassLoader parent = JavaRun.class.getClassLoader();
+ loader = new WeavingURLClassLoader(classURLs, aspectURLs, parent);
+ }
+ // 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);
+ RunSecurityManager.ME.setJavaRunThread(this);
+ main.invoke(null, new Object[] { spec.getOptionsArray() });
+ completedNormally = true;
+ boolean snoopFailure =
+ ((null != errSnoop) && 0 < errSnoop.size())
+ || ((null != outSnoop) && 0 < outSnoop.size());
+ passed = !snoopFailure && (null == spec.expectedException);
+ } catch (AbortException e) {
+ if (expectedException(e)) {
+ passed = true;
+ } else {
+ throw e;
+ }
+ } catch (InvocationTargetException e) {
+ // this and following clauses catch ExitCalledException
+ Throwable thrown = LangUtil.unwrapException(e);
+ if (null == thrown) {
+ throw e;
+ }
+ if (thrown instanceof RunSecurityManager.ExitCalledException) {
+ int i = ((RunSecurityManager.ExitCalledException) thrown).exitCode;
+ status.finish(new Integer(i));
+ } else if (thrown instanceof RunSecurityManager.AwtUsedException) {
+ MessageUtil.fail(status, "test code should not use the AWT event queue");
+ throw (RunSecurityManager.AwtUsedException) thrown;
+ // same as: status.thrown(thrown);
+ } else if (expectedException(thrown)) {
+ passed = true;
+ } else if (thrown instanceof RuntimeException) {
+ throw (RuntimeException) thrown;
+ } else if (thrown instanceof Error) {
+ throw (Error) thrown;
+ } else {
+ throw e;
+ }
+ } catch (RunSecurityManager.ExitCalledException e) {
+ // XXX need to update run validator (a) to accept null result or (b) to require zero result, and set 0 if completed normally
+ status.finish(new Integer(e.exitCode));
+ } catch (ClassNotFoundException e) {
+ String[] classes = FileUtil.listFiles(sandbox.classesDir);
+ MessageUtil.info(status, "sandbox.classes: " + Arrays.asList(classes));
+ MessageUtil.fail(status, null, e);
+ } finally {
+ if (null != oldOut) {
+ System.setOut(oldOut);
+ }
+ if (null != oldErr) {
+ System.setErr(oldErr);
+ }
+ RunSecurityManager.ME.releaseJavaRunThread(this);
+ if (!completedNormally) {
+ MessageUtil.info(status, "targetClass: " + targetClass);
+ MessageUtil.info(status, "loader: " + loader);
+ }
+ }
+ return passed;
+ }
+
+ /**
+ * Run in another VM by grabbing Java, bootclasspath, classpath, etc.
+ * This assumes any exception or output to System.err is a failure,
+ * and any normal completion is a pass.
+ * @param status
+ * @param libs
+ * @param dirs
+ * @return
+ */
+ protected boolean runInOtherVM(IRunStatus status, File[] libs, File[] dirs) {
+ // assert spec.fork || !LangUtil.isEmpty(spec.aspectpath);
+ ArrayList cmd = new ArrayList();
+ cmd.add(FileUtil.getBestPath(spec.forkSpec.java));
+ if (!LangUtil.isEmpty(spec.forkSpec.vmargs)) {
+ cmd.addAll(Arrays.asList(spec.forkSpec.vmargs));
+ }
+ final String classpath;
+ {
+ StringBuffer cp = new StringBuffer();
+ appendClasspath(cp, spec.forkSpec.bootclasspath);
+ appendClasspath(cp, dirs);
+ appendClasspath(cp, libs);
+ File[] classpathFiles = sandbox.findFiles(spec.classpath);
+ int cpLength = (null == classpathFiles ? 0 : classpathFiles.length);
+ int spLength = (null == spec.classpath ? 0 : spec.classpath.length);
+ if (cpLength != spLength) {
+ throw new Error("unable to find " + Arrays.asList(spec.classpath)
+ + " got " + Arrays.asList(classpathFiles));
+ }
+ appendClasspath(cp, classpathFiles);
+ File[] stdlibs = {Globals.F_aspectjrt_jar, Globals.F_testingclient_jar};
+ appendClasspath(cp, stdlibs);
+ classpath = cp.toString();
+ }
+ if (!spec.isLTW()) {
+ cmd.add("-classpath");
+ cmd.add(classpath);
+ } else {
+ // verify 1.4 or above, assuming same vm as running this
+ if (!Globals.supportsJava("1.4")) {
+ throw new Error("load-time weaving test requires Java 1.4+");
+ }
+ cmd.add("-Djava.system.class.loader=org.aspectj.weaver.WeavingURLClassLoader");
+ // assume harness VM classpath has WeavingURLClassLoader (but not others)
+ cmd.add("-classpath");
+ cmd.add(System.getProperty("java.class.path"));
+
+ File[] aspectJars = sandbox.findFiles(spec.aspectpath);
+ if (aspectJars.length != spec.aspectpath.length) {
+ throw new Error("unable to find " + Arrays.asList(spec.aspectpath));
+ }
+ StringBuffer cp = new StringBuffer();
+ appendClasspath(cp, aspectJars);
+ cmd.add("-Daj.aspect.path=" + cp.toString());
+ cp.append(classpath); // appendClasspath writes trailing delimiter
+ cmd.add("-Daj.class.path=" + cp.toString());
+ }
+ cmd.add(spec.className);
+ cmd.addAll(spec.options);
+ String[] command = (String[]) cmd.toArray(new String[0]);
+
+ final IMessageHandler handler = status;
+ // setup to run asynchronously, pipe streams through, and report errors
+ class DoneFlag {
+ boolean done;
+ boolean failed;
+ int code;
+ }
+ final StringBuffer commandLabel = new StringBuffer();
+ final DoneFlag doneFlag = new DoneFlag();
+ LangUtil.ProcessController controller
+ = new LangUtil.ProcessController() {
+ protected void doCompleting(Thrown ex, int result) {
+ if (!ex.thrown && (0 == result)) {
+ doneFlag.done = true;
+ return; // no errors
+ }
+ // handle errors
+ String context = spec.className
+ + " command \""
+ + commandLabel
+ + "\"";
+ if (null != ex.fromProcess) {
+ if (!expectedException(ex.fromProcess)) {
+ String m = "Exception running " + context;
+ MessageUtil.abort(handler, m, ex.fromProcess);
+ doneFlag.failed = true;
+ }
+ } else if (0 != result) {
+ doneFlag.code = result;
+ }
+ if (null != ex.fromInPipe) {
+ String m = "Error processing input pipe for " + context;
+ MessageUtil.abort(handler, m, ex.fromInPipe);
+ doneFlag.failed = true;
+ }
+ if (null != ex.fromOutPipe) {
+ String m = "Error processing output pipe for " + context;
+ MessageUtil.abort(handler, m, ex.fromOutPipe);
+ doneFlag.failed = true;
+ }
+ if (null != ex.fromErrPipe) {
+ String m = "Error processing error pipe for " + context;
+ MessageUtil.abort(handler, m, ex.fromErrPipe);
+ doneFlag.failed = true;
+ }
+ doneFlag.done = true;
+ }
+ };
+ controller.init(command, spec.className);
+ if (null != spec.forkSpec.javaHome) {
+ controller.setEnvp(new String[] {"JAVA_HOME=" + spec.forkSpec.javaHome});
+ }
+ commandLabel.append(Arrays.asList(controller.getCommand()).toString());
+ final ByteArrayOutputStream errSnoop
+ = new ByteArrayOutputStream();
+ final ByteArrayOutputStream outSnoop
+ = new ByteArrayOutputStream();
+ controller.setErrSnoop(errSnoop);
+ controller.setOutSnoop(outSnoop);
+ controller.start();
+ // give it 3 minutes...
+ long maxTime = System.currentTimeMillis() + 3 * 60 * 1000;
+ boolean waitingForStop = false;
+ while (!doneFlag.done) {
+ if (maxTime < System.currentTimeMillis()) {
+ if (waitingForStop) { // hit second timeout - bail
+ break;
+ }
+ MessageUtil.fail(status, "timeout waiting for process");
+ doneFlag.failed = true;
+ controller.stop();
+ // wait 1 minute to evaluate results of stopping
+ waitingForStop = true;
+ maxTime = System.currentTimeMillis() + 1 * 60 * 1000;
+ }
+ try {
+ Thread.sleep(300);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ }
+
+ boolean foundException = false;
+ if (0 < errSnoop.size()) {
+ if (expectedException(errSnoop)) {
+ foundException = true;
+ } else if (spec.errStreamIsError) {
+ MessageUtil.error(handler, errSnoop.toString());
+ if (!doneFlag.failed) {
+ doneFlag.failed = true;
+ }
+ } else {
+ MessageUtil.info(handler, "Error stream: " + errSnoop.toString());
+ }
+ }
+ if (0 < outSnoop.size()) {
+ if (expectedException(outSnoop)) {
+ foundException = true;
+ } else if (spec.outStreamIsError) {
+ MessageUtil.error(handler, outSnoop.toString());
+ if (!doneFlag.failed) {
+ doneFlag.failed = true;
+ }
+ } else {
+ MessageUtil.info(handler, "Output stream: " + outSnoop.toString());
+ }
+ }
+ if (!foundException) {
+ if (null != spec.expectedException) {
+ String m = " expected exception " + spec.expectedException;
+ MessageUtil.fail(handler, m);
+ doneFlag.failed = true;
+ } else if (0 != doneFlag.code) {
+ String m = doneFlag.code + " result from " + commandLabel;
+ MessageUtil.fail(handler, m);
+ doneFlag.failed = true;
+ }
+ }
+ if (doneFlag.failed) {
+ MessageUtil.info(handler, "other-vm command-line: " + commandLabel);
+ }
+ return !doneFlag.failed;
+ }
+
+ protected boolean expectedException(Throwable thrown) {
+ if (null != spec.expectedException) {
+ String cname = thrown.getClass().getName();
+ if (-1 != cname.indexOf(spec.expectedException)) {
+ return true; // caller sets value for returns normally
+ }
+ }
+ return false;
+ }
+
+ protected boolean expectedException(ByteArrayOutputStream bout) {
+ return ((null != spec.expectedException)
+ && (-1 != bout.toString().indexOf(spec.expectedException)));
+ }
+
+ /**
+ * 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 + ")";
+ }
+
+ /**
+ * Struct class for fork attributes and initialization.
+ * This supports defaults for forking using system properties
+ * which will be overridden by any specification.
+ * (It differs from CompilerRun, which supports option
+ * overriding by passing values as harness arguments.)
+ */
+ public static class ForkSpec {
+ /**
+ * key for system property for default value for forking
+ * (true if set to true)
+ */
+ public static String FORK_KEY = "javarun.fork";
+ public static String JAVA_KEY = "javarun.java";
+ public static String VM_ARGS_KEY = "javarun.vmargs";
+ public static String JAVA_HOME_KEY = "javarun.java.home";
+ public static String BOOTCLASSPATH_KEY = "javarun.bootclasspath";
+ static final ForkSpec FORK;
+ static {
+ ForkSpec fork = new ForkSpec();
+ fork.fork = Boolean.getBoolean(FORK_KEY);
+ fork.java = getFile(JAVA_KEY);
+ if (null == fork.java) {
+ fork.java = LangUtil.getJavaExecutable();
+ }
+ fork.javaHome = getFile(JAVA_HOME_KEY);
+ fork.bootclasspath = XMLWriter.unflattenList(getProperty(BOOTCLASSPATH_KEY));
+ fork.vmargs = XMLWriter.unflattenList(getProperty(VM_ARGS_KEY));
+ FORK = fork;
+ }
+ private static File getFile(String key) {
+ String path = getProperty(key);
+ if (null != path) {
+ File result = new File(path);
+ if (result.exists()) {
+ return result;
+ }
+ }
+ return null;
+ }
+ private static String getProperty(String key) {
+ try {
+ return System.getProperty(key);
+ } catch (Throwable t) {
+ return null;
+ }
+ }
+ private boolean fork;
+ private String[] bootclasspath;
+ private File java;
+ private File javaHome;
+ private String[] vmargs;
+
+ private ForkSpec() {
+ copy(FORK);
+ }
+
+ private void copy(ForkSpec forkSpec) {
+ if (null != forkSpec) {
+ fork = forkSpec.fork;
+ bootclasspath = forkSpec.bootclasspath;
+ java = forkSpec.java;
+ javaHome = forkSpec.javaHome;
+ vmargs = forkSpec.vmargs;
+ }
+ }
+
+ /**
+ * @return "" or bootclasspath with File.pathSeparator internal delimiters
+ */
+ String getBootclasspath() {
+ if (LangUtil.isEmpty(bootclasspath)) {
+ return "";
+ }
+ return FileUtil.flatten(bootclasspath, null);
+ }
+ }
+
+ /**
+ * Initializer/factory for JavaRun.
+ * The classpath is not here but precalculated in the Sandbox.
+ */
+ public static class Spec extends AbstractRunSpec {
+ static {
+ try {
+ System.setSecurityManager(RunSecurityManager.ME);
+ } catch (Throwable t) {
+ System.err.println("JavaRun: Security manager set - no System.exit() protection");
+ }
+ }
+ public static final String XMLNAME = "run";
+ /**
+ * skip description, skip sourceLocation,
+ * do keywords, do options, skip paths, do comment,
+ * skip staging, skip badInput,
+ * 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;
+
+ /** Alternative to classname for specifying what to run modulename/type */
+ protected String module;
+
+ /** minimum required version of Java, if any */
+ protected String javaVersion;
+
+ /** if true, skip Tester setup (e.g., if Tester n/a) */
+ protected boolean skipTester;
+
+ /** if true, report text to output stream as error */
+ protected boolean outStreamIsError;
+
+ /** if true, report text to error stream as error */
+ protected boolean errStreamIsError = true;
+
+ protected final ForkSpec forkSpec;
+ protected String[] aspectpath;
+ protected boolean useLTW;
+ protected String[] classpath;
+ protected String expectedException;
+
+ public Spec() {
+ super(XMLNAME);
+ setXMLNames(NAMES);
+ forkSpec = new ForkSpec();
+ }
+
+ protected void initClone(Spec spec)
+ throws CloneNotSupportedException {
+ super.initClone(spec);
+ spec.className = className;
+ spec.errStreamIsError = errStreamIsError;
+ spec.javaVersion = javaVersion;
+ spec.outStreamIsError = outStreamIsError;
+ spec.skipTester = skipTester;
+ spec.forkSpec.copy(forkSpec);
+ }
+
+ public Object clone() throws CloneNotSupportedException {
+ Spec result = new Spec();
+ initClone(result);
+ return result;
+ }
+
+ public boolean isLTW() {
+ return useLTW || (null != aspectpath);
+ }
+
+ /**
+ * @param version "1.1", "1.2", "1.3", "1.4"
+ * @throws IllegalArgumentException if version is not recognized
+ */
+ public void setJavaVersion(String version) {
+ Globals.supportsJava(version);
+ this.javaVersion = version;
+ }
+
+ /** @className fully-qualified name of the class to run */
+ public void setClassName(String className) {
+ this.className = className;
+ }
+
+ public void setModule(String module) {
+ this.module = module;
+ }
+
+ public void setLTW(String ltw) {
+ useLTW = TestUtil.parseBoolean(ltw);
+ }
+
+ public void setAspectpath(String path) {
+ this.aspectpath = XMLWriter.unflattenList(path);
+ }
+ public void setException(String exception) {
+ this.expectedException = exception;
+ }
+
+ public void setClasspath(String path) {
+ this.classpath = XMLWriter.unflattenList(path);
+ }
+ public void setErrStreamIsError(String errStreamIsError) {
+ this.errStreamIsError = TestUtil.parseBoolean(errStreamIsError);
+ }
+
+ public void setOutStreamIsError(String outStreamIsError) {
+ this.outStreamIsError = TestUtil.parseBoolean(outStreamIsError);
+ }
+
+ /** @param skip if true, then do not set up Tester */
+ public void setSkipTester(boolean skip) {
+ skipTester = skip;
+ }
+
+ public void setFork(boolean fork) {
+ forkSpec.fork = fork;
+ }
+
+ /**
+ * @param vmargs comma-delimited list of arguments for java,
+ * typically -Dname=value,-DanotherName="another value"
+ */
+ public void setVmArgs(String vmargs) {
+ forkSpec.vmargs = XMLWriter.unflattenList(vmargs);
+ }
+
+ /** 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 = XMLWriter.makeAttribute("class", className);
+ out.startElement(xmlElementName, attr, false);
+ if (skipTester) {
+ out.printAttribute("skipTester", "true");
+ }
+ if (null != javaVersion) {
+ out.printAttribute("vm", javaVersion);
+ }
+ if (outStreamIsError) {
+ out.printAttribute("outStreamIsError", "true");
+ }
+ if (!errStreamIsError) { // defaults to true
+ out.printAttribute("errStreamIsError", "false");
+ }
+ super.writeAttributes(out);
+ out.endAttributes();
+ if (!LangUtil.isEmpty(dirChanges)) {
+ DirChanges.Spec.writeXml(out, dirChanges);
+ }
+ SoftMessage.writeXml(out, getMessages());
+ 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) && (!Globals.supportsJava(javaVersion))) {
+ skipMessage(handler, "requires Java version " + javaVersion);
+ return false;
+ }
+ return true;
+ }
+ }
+ /**
+ * This permits everything but System.exit() in the context of a
+ * thread set by JavaRun.
+ * XXX need to update for thread spawned by that thread
+ * XXX need to update for awt thread use after AJDE wrapper doesn't
+ */
+ public static class RunSecurityManager extends SecurityManager {
+ public static RunSecurityManager ME = new RunSecurityManager();
+ private Thread runThread;
+ private RunSecurityManager(){}
+ private synchronized void setJavaRunThread(JavaRun run) {
+ LangUtil.throwIaxIfNull(run, "run");
+ runThread = Thread.currentThread();
+ }
+ private synchronized void releaseJavaRunThread(JavaRun run) {
+ LangUtil.throwIaxIfNull(run, "run");
+ runThread = null;
+ }
+ /** @throws ExitCalledException if called from the JavaRun-set thread */
+ public void checkExit(int exitCode) throws ExitCalledException {
+ if ((null != runThread) && runThread.equals(Thread.currentThread())) {
+ throw new ExitCalledException(exitCode);
+ }
+ }
+ public void checkAwtEventQueueAccess() {
+ if ((null != runThread) && runThread.equals(Thread.currentThread())) {
+ throw new AwtUsedException();
+ }
+ }
+ public void checkSystemClipboardAccess() {
+ // permit
+ }
+ // used by constrained calls
+ public static class ExitCalledException extends SecurityException {
+ public final int exitCode;
+ public ExitCalledException(int exitCode) {
+ this.exitCode = exitCode;
+ }
+ }
+ public static class AwtUsedException extends SecurityException {
+ public AwtUsedException() { }
+ }
+ // permit everything else
+ public void checkAccept(String arg0, int arg1) {
+ }
+ public void checkAccess(Thread arg0) {
+ }
+ public void checkAccess(ThreadGroup arg0) {
+ }
+ public void checkConnect(String arg0, int arg1) {
+ }
+ public void checkConnect(String arg0, int arg1, Object arg2) {
+ }
+ public void checkCreateClassLoader() {
+ }
+ public void checkDelete(String arg0) {
+ }
+ public void checkExec(String arg0) {
+ }
+ public void checkLink(String arg0) {
+ }
+ public void checkListen(int arg0) {
+ }
+ public void checkMemberAccess(Class arg0, int arg1) {
+ }
+ public void checkMulticast(InetAddress arg0) {
+ }
+ public void checkMulticast(InetAddress arg0, byte arg1) {
+ }
+ public void checkPackageAccess(String arg0) {
+ }
+ public void checkPackageDefinition(String arg0) {
+ }
+ public void checkPermission(Permission arg0) {
+ }
+ public void checkPermission(Permission arg0, Object arg1) {
+ }
+ public void checkPrintJobAccess() {
+ }
+ public void checkPropertiesAccess() {
+ }
+ public void checkPropertyAccess(String arg0) {
+ }
+ public void checkRead(FileDescriptor arg0) {
+ }
+ public void checkRead(String arg0) {
+ }
+ public void checkRead(String arg0, Object arg1) {
+ }
+ public void checkSecurityAccess(String arg0) {
+ }
+ public void checkSetFactory() {
+ }
+ public boolean checkTopLevelWindow(Object arg0) {
+ return true;
+ }
+ public void checkWrite(FileDescriptor arg0) {
+ }
+ public void checkWrite(String arg0) {
+ }
+
+ }
+
+}
diff --git a/testing/src/test/java/org/aspectj/testing/harness/bridge/RunSpecIterator.java b/testing/src/test/java/org/aspectj/testing/harness/bridge/RunSpecIterator.java
new file mode 100644
index 000000000..e5792236b
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import java.util.ArrayList;
+
+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;
+
+
+/**
+ * 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/test/java/org/aspectj/testing/harness/bridge/Sandbox.java b/testing/src/test/java/org/aspectj/testing/harness/bridge/Sandbox.java
new file mode 100644
index 000000000..f7f4df31e
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/harness/bridge/Sandbox.java
@@ -0,0 +1,575 @@
+/* *******************************************************************
+ * 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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import org.aspectj.bridge.ICommand;
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.testing.taskdefs.AjcTaskCompileCommand;
+import org.aspectj.testing.util.Diffs;
+import org.aspectj.util.FileUtil;
+import org.aspectj.util.LangUtil;
+
+/**
+ * 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[] compileClasspath;
+
+ private String bootClasspath;
+
+ /** aspectpath entries, set by CompileRun.setup(..) */
+ private File[] aspectpath;
+
+ /** 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;
+
+ private transient boolean compilerRunInit;
+
+ /** @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;
+ {
+ File baseDir = FileUtil.getTempDir("Sandbox");
+ if (!baseDir.isAbsolute()) {
+ baseDir = baseDir.getAbsoluteFile();
+ }
+ sandboxDir = baseDir;
+ }
+ 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) { // need to add ICommand.quit()
+ if (command instanceof AjcTaskCompileCommand) { // XXX urk!
+ ((AjcTaskCompileCommand) command).quit();
+ }
+ command = null;
+ }
+ // also try to clear sandbox/filesystem.
+ // If locked by suite, we can't.
+ if (null != validator) {
+ validator.deleteTempFiles(true);
+ }
+ }
+
+// /**
+// * 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(CompilerRun caller) {
+ LangUtil.throwIaxIfNull(caller, "caller");
+ assertState(null != command, "command never set");
+ return command;
+ }
+
+ 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;
+ }
+
+ /**
+ * Get the files with names (case-sensitive)
+ * under the staging or test base directories.
+ * @param names
+ * @return
+ */
+ File[] findFiles(final String[] names) {
+ ArrayList result = new ArrayList();
+ NamesFilter filter = new NamesFilter(names);
+ File[] bases = { testBaseDir, sandboxDir };
+ for (int i = 0; i < bases.length; i++) {
+ File base = bases[i];
+ if ((null == base) || !base.canRead()) {
+ continue;
+ }
+ result.addAll(Arrays.asList(FileUtil.listFiles(base, filter)));
+ }
+ return (File[]) result.toArray(new File[0]);
+ }
+ File getTestBaseSrcDir(JavaRun caller) {
+ LangUtil.throwIaxIfNull(caller, "caller");
+ return testBaseSrcDir;
+ }
+
+ void defaultTestBaseSrcDir(JavaRun caller) {
+ LangUtil.throwIaxIfNull(caller, "caller");
+ if (null != testBaseSrcDir) {
+ throw new IllegalStateException("testBaseSrcDir not null");
+ }
+ testBaseSrcDir = testBaseDir;
+ }
+
+ static boolean readableDir(File dir) {
+ return ((null != dir) && dir.isDirectory() && dir.canRead());
+ }
+
+ void compilerRunInit(CompilerRun caller, File testBaseSrcDir,
+ File[] aspectPath, boolean aspectpathReadable,
+ File[] classPath, boolean classpathReadable,
+ String bootclassPath
+ ) {
+ if (null != testBaseSrcDir) {
+ setTestBaseSrcDir(testBaseSrcDir, caller);
+ }
+ if ((null != aspectPath) && (0 < aspectPath.length)) {
+ setAspectpath(aspectPath, aspectpathReadable, caller);
+ }
+ if ((null != classPath) && (0 < classPath.length)) {
+ setClasspath(classPath, classpathReadable, caller);
+ }
+
+ setBootclasspath(bootclassPath, caller);
+ compilerRunInit = true;
+ }
+ void javaRunInit(JavaRun caller) {
+ if (!compilerRunInit) {
+ testBaseSrcDir = testBaseDir;
+ // default to aspectjrt.jar?
+ compileClasspath = new File[0];
+
+ }
+ }
+
+ /** @throws IllegalArgumentException unless a readable directory */
+ private 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;
+ }
+
+ /**
+ * Set aspectpath.
+ * @param readable if true, then throw IllegalArgumentException if not readable
+ */
+ private void setAspectpath(File[] files, boolean readable, CompilerRun caller) {
+ LangUtil.throwIaxIfNull(files, "files");
+ LangUtil.throwIaxIfNull(caller, "caller");
+ assertState(null == aspectpath, "aspectpath already written");
+ aspectpath = 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 aspectpath entry: " + files[i]);
+ }
+ aspectpath[i] = files[i];
+ }
+ }
+
+ /**
+ * Set bootclasspath, presumed to be delimited by
+ * File.pathSeparator and have valid entries.
+ * @param bootClasspath
+ * @param caller
+ */
+ private void setBootclasspath(String bootClasspath, CompilerRun caller) {
+ this.bootClasspath = bootClasspath;
+ }
+
+ /**
+ * Set compile classpath.
+ * @param readable if true, then throw IllegalArgumentException if not readable
+ */
+ private void setClasspath(File[] files, boolean readable, CompilerRun caller) {
+ LangUtil.throwIaxIfNull(files, "files");
+ LangUtil.throwIaxIfNull(caller, "caller");
+ assertState(!gotClasspath, "classpath already read");
+ compileClasspath = 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]);
+ }
+ compileClasspath[i] = files[i];
+ }
+ }
+
+// /**
+// * Get run classpath
+// * @param caller unused except to restrict usage to non-null JavaRun.
+// * @throws IllegalStateException if compileClasspath was not set.
+// * @throws IllegalArgumentException if caller is null
+// */
+// File[] getRunClasspath(JavaRun caller) {
+// LangUtil.throwIaxIfNull(caller, "caller");
+// assertState(null != compileClasspath, "classpath not set");
+// int compilePathLength = compileClasspath.length;
+// int aspectPathLength = (null == aspectpath ? 0 : aspectpath.length);
+// File[] result = new File[aspectPathLength + compilePathLength];
+// System.arraycopy(compileClasspath, 0, result, 0, compilePathLength);
+// if (0 < aspectPathLength) {
+// System.arraycopy(aspectpath, 0, result, compilePathLength, aspectPathLength);
+// }
+// return result;
+// }
+
+ /**
+ * Get directories for the run classpath by selecting them
+ * from the compile classpath.
+ * This ignores aspectpath since it may contain only jar files.
+ * @param readable if true, omit non-readable directories
+ */
+ File[] getClasspathDirectories(
+ boolean readable,
+ JavaRun caller,
+ boolean includeOutput) {
+ LangUtil.throwIaxIfNull(caller, "caller");
+ assertState(null != compileClasspath, "classpath not set");
+ ArrayList result = new ArrayList();
+ File[] src = compileClasspath;
+ for (int i = 0; i < src.length; i++) {
+ File f = src[i];
+ if ((null != f) && (f.isDirectory()) && (!readable || f.canRead())) {
+ result.add(f);
+ }
+ }
+ if (includeOutput && (null != classesDir)
+ && (!readable || classesDir.canRead())) {
+ result.add(classesDir);
+ }
+ return (File[]) result.toArray(new File[0]);
+ }
+
+ /**
+ * Get the jars belonging on the run classpath, including classpath
+ * and aspectpath entries.
+ * @param readable if true, omit non-readable directories
+ */
+ File[] getClasspathJars(boolean readable, JavaRun caller) {
+ LangUtil.throwIaxIfNull(caller, "caller");
+ assertState(null != compileClasspath, "classpath not set");
+ ArrayList result = new ArrayList();
+ File[][] src = new File[][] { compileClasspath, aspectpath };
+ for (int i = 0; i < src.length; i++) {
+ File[] paths = src[i];
+ int len = (null == paths ? 0 : paths.length);
+ for (int j = 0; j < len; j++) {
+ File f = paths[j];
+ if (FileUtil.isZipFile(f) && (!readable || f.canRead())) {
+ result.add(f);
+ }
+ }
+ }
+ return (File[]) result.toArray(new File[0]);
+ }
+
+ /**
+ * Get the list of aspect jars as a String.
+ * @return String of classpath entries delimited internally by File.pathSeparator
+ */
+ String aspectpathToString(CompilerRun caller) {
+ LangUtil.throwIaxIfNull(caller, "caller");
+ return FileUtil.flatten(aspectpath, File.pathSeparator);
+ }
+
+ /**
+ * Get the compile classpath as a String.
+ * @return String of classpath entries delimited internally by File.pathSeparator
+ */
+ String classpathToString(CompilerRun caller) {
+ LangUtil.throwIaxIfNull(caller, "caller");
+ return FileUtil.flatten(compileClasspath, File.pathSeparator);
+ }
+
+ /**
+ * Get the bootClasspath as a String.
+ * @return String of bootclasspath entries delimited internally by File.pathSeparator
+ */
+ String getBootclasspath(CompilerRun caller) {
+ LangUtil.throwIaxIfNull(caller, "caller");
+ return bootClasspath;
+ }
+
+ /**
+ * Get the bootClasspath as a String.
+ * @return String of bootclasspath entries delimited internally by File.pathSeparator
+ */
+ String getBootclasspath(JavaRun caller) {
+ LangUtil.throwIaxIfNull(caller, "caller");
+ return bootClasspath;
+ }
+ private static class NamesFilter implements FileFilter {
+ private final String[] names;
+ private NamesFilter(String[] names) {
+ this.names = names;
+ }
+ public boolean accept(File file) {
+ if (null != file) {
+ String name = file.getName();
+ if ((null != name) && (null != names)) {
+ for (int i = 0; i < names.length; i++) {
+ if (name.equals(names[i])) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/testing/src/test/java/org/aspectj/testing/harness/bridge/TestingBridgeTests.java b/testing/src/test/java/org/aspectj/testing/harness/bridge/TestingBridgeTests.java
deleted file mode 100644
index 1fd4cb342..000000000
--- a/testing/src/test/java/org/aspectj/testing/harness/bridge/TestingBridgeTests.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/* *******************************************************************
- * 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 Eclipse Public License v1.0
- * which accompanies this distribution and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- * Xerox/PARC initial implementation
- * ******************************************************************/
-
-
-package org.aspectj.testing.harness.bridge;
-
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-
-public class TestingBridgeTests extends TestCase {
-
- public static Test suite() {
- TestSuite suite = new TestSuite(TestingBridgeTests.class.getName());
- // XXX disabled DirChangesTest pending implementation
- //$JUnit-BEGIN$
- suite.addTestSuite(AbstractRunSpecTest.class);
- suite.addTestSuite(AjcSpecTest.class);
- suite.addTestSuite(CompilerRunTest.class);
- suite.addTestSuite(CompilerRunSpecTest.class);
- suite.addTestSuite(ParseTestCase.class);
- //$JUnit-END$
- return suite;
- }
-
- public TestingBridgeTests(String name) { super(name); }
-
-}
diff --git a/testing/src/test/java/org/aspectj/testing/harness/bridge/Validator.java b/testing/src/test/java/org/aspectj/testing/harness/bridge/Validator.java
new file mode 100644
index 000000000..37ddd005e
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/harness/bridge/Validator.java
@@ -0,0 +1,564 @@
+/* *******************************************************************
+ * 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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.harness.bridge;
+
+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;
+
+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;
+
+/**
+ * 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 {
+ 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();) {
+ if (deleteFile((File) iter.next(), reportFailures)) {
+ iter.remove();
+ }
+ }
+ for (ListIterator iter = sandboxes.listIterator(); iter.hasNext();) {
+ Sandbox sandbox = (Sandbox) iter.next();
+ // XXX assumes all dirs are in sandboxDir
+ if (deleteFile(sandbox.sandboxDir, reportFailures)) {
+ iter.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * 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 boolean deleteFile(File file, boolean reportFailures) {
+ if (null == file) {
+ if (reportFailures) {
+ fail("unable to delete null file");
+ }
+ return true; // null file - skip
+ }
+ FileUtil.deleteContents(file);
+ if (file.exists()) {
+ file.delete();
+ }
+ if (!file.exists()) {
+ return true;
+ } else if (reportFailures) {
+ fail("unable to delete " + file);
+ }
+ return false;
+ }
+
+ /** @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/test/java/org/aspectj/testing/run/IRun.java b/testing/src/test/java/org/aspectj/testing/run/IRun.java
new file mode 100644
index 000000000..57639016b
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/run/IRunIterator.java b/testing/src/test/java/org/aspectj/testing/run/IRunIterator.java
new file mode 100644
index 000000000..5035b216f
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/run/IRunListener.java b/testing/src/test/java/org/aspectj/testing/run/IRunListener.java
new file mode 100644
index 000000000..4318d618f
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/run/IRunStatus.java b/testing/src/test/java/org/aspectj/testing/run/IRunStatus.java
new file mode 100644
index 000000000..9c8665728
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/run/IRunStatus.java
@@ -0,0 +1,195 @@
+/* *******************************************************************
+ * 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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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
+ /**
+ * Detect whether a message of a given kind has been handled.
+ * @param kind the IMessage.Kind of the message to detect
+ * @param orGreater if true, then also accept any message of a greater kind
+ * @param includeChildren if true, then also search in any child IRunStatus
+ * @return true if any such message is detected
+ */
+ boolean hasAnyMessage(IMessage.Kind kind, boolean orGreater, boolean includeChildren);
+
+ /**
+ * Get all messages or those of a specific kind, optionally in children as well
+ * Pass null to get all kinds.
+ * @param kind the IMessage.Kind expected, or null for all messages
+ * @param orGreater if true, also get any greater than the target kind
+ * as determined by IMessage.Kind.COMPARATOR
+ * @param includeChildren if true, then also search in any child IRunStatus
+ * @return IMessage[] of messages of the right kind, or IMessage.NONE
+ */
+ IMessage[] getMessages(IMessage.Kind kind, boolean orGreater, boolean includeChildren);
+
+ /**
+ * 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/test/java/org/aspectj/testing/run/IRunValidator.java b/testing/src/test/java/org/aspectj/testing/run/IRunValidator.java
new file mode 100644
index 000000000..a341aa167
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/run/RunIterator.java b/testing/src/test/java/org/aspectj/testing/run/RunIterator.java
new file mode 100644
index 000000000..1b5c8f307
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.run;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.bridge.MessageUtil;
+import org.aspectj.util.LangUtil;
+
+/**
+ * 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/test/java/org/aspectj/testing/run/RunListener.java b/testing/src/test/java/org/aspectj/testing/run/RunListener.java
new file mode 100644
index 000000000..0d7c9fc76
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.run;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+import org.aspectj.bridge.AbortException;
+import org.aspectj.bridge.MessageUtil;
+
+/**
+ * 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/test/java/org/aspectj/testing/run/RunListeners.java b/testing/src/test/java/org/aspectj/testing/run/RunListeners.java
new file mode 100644
index 000000000..4eb8e7a74
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/run/RunStatus.java b/testing/src/test/java/org/aspectj/testing/run/RunStatus.java
new file mode 100644
index 000000000..ac22280ba
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/run/RunStatus.java
@@ -0,0 +1,442 @@
+/* *******************************************************************
+ * 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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.run;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.IMessageHolder;
+import org.aspectj.bridge.MessageHandler;
+import org.aspectj.testing.util.BridgeUtil;
+import org.aspectj.util.LangUtil;
+
+/**
+ * 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.run.IRunStatus#runResult()
+ */
+ public boolean runResult() {
+ return validator.runPassed(this);
+ }
+
+ public boolean hasAnyMessage(IMessage.Kind kind, boolean orGreater, boolean includeChildren) {
+ if (messageHolder.hasAnyMessage(kind, orGreater)) {
+ return true;
+ }
+ if (includeChildren) {
+ IRunStatus[] kids = getChildren();
+ for (int i = 0; i < kids.length; i++) {
+ if (kids[i].hasAnyMessage(kind, orGreater, true)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public IMessage[] getMessages(IMessage.Kind kind, boolean orGreater, boolean includeChildren) {
+ IMessage[] result = getMessages(kind, orGreater);
+ if (!includeChildren) {
+ return result;
+ }
+ ArrayList sink = new ArrayList();
+ if (!LangUtil.isEmpty(result)) {
+ sink.addAll(Arrays.asList(result));
+ }
+
+ IRunStatus[] kids = getChildren();
+ for (int i = 0; i < kids.length; i++) {
+ result = kids[i].getMessages(kind, orGreater, includeChildren);
+ if (!LangUtil.isEmpty(result)) {
+ sink.addAll(Arrays.asList(result));
+ }
+ }
+ return (IMessage[]) sink.toArray(new IMessage[0]);
+ }
+
+ // ------------------- 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);
+ }
+
+ public void dontIgnore(IMessage.Kind kind) {
+ messageHolder.dontIgnore(kind);
+ }
+
+ public void ignore(IMessage.Kind kind) {
+ messageHolder.ignore(kind);
+ }
+
+ /**
+ * @see org.aspectj.bridge.IMessageHolder#hasAnyMessage(org.aspectj.bridge.IMessage.Kind, boolean)
+ */
+ public boolean hasAnyMessage(IMessage.Kind kind, boolean orGreater) {
+ return messageHolder.hasAnyMessage(kind, orGreater);
+ }
+
+ /**
+ * @see org.aspectj.bridge.IMessageHolder#getMessages(org.aspectj.bridge.IMessage.Kind, boolean)
+ */
+ public IMessage[] getMessages(IMessage.Kind kind, boolean orGreater) {
+ return messageHolder.getMessages(kind, orGreater);
+ }
+
+ /**
+ * @see org.aspectj.bridge.IMessageHolder#numMessages(org.aspectj.bridge.IMessage.Kind, boolean)
+ */
+ 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<IMessage> 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/test/java/org/aspectj/testing/run/RunValidator.java b/testing/src/test/java/org/aspectj/testing/run/RunValidator.java
new file mode 100644
index 000000000..70131f71a
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/run/RunValidator.java
@@ -0,0 +1,210 @@
+/* *******************************************************************
+ * 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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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,
+ * except that Integer Objects must have value 0 */
+ public static final IRunValidator NORMAL
+ = new RunValidator(ObjectChecker.ANY_ZERO);
+ /** expect normal completion with any non-null result object */
+ public static final IRunValidator ORIGINAL_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/test/java/org/aspectj/testing/run/Runner.java b/testing/src/test/java/org/aspectj/testing/run/Runner.java
new file mode 100644
index 000000000..58860be6f
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.run;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+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;
+
+/**
+ * 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/test/java/org/aspectj/testing/run/WrappedRunIterator.java b/testing/src/test/java/org/aspectj/testing/run/WrappedRunIterator.java
new file mode 100644
index 000000000..75be92cb7
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/taskdefs/AjcTaskCompileCommand.java b/testing/src/test/java/org/aspectj/testing/taskdefs/AjcTaskCompileCommand.java
new file mode 100644
index 000000000..1f5325e82
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/taskdefs/AjcTaskCompileCommand.java
@@ -0,0 +1,294 @@
+/* *******************************************************************
+ * Copyright (c) 2003 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Wes Isberg initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.taskdefs;
+
+//import java.awt.Frame;
+import java.io.*;
+//import java.lang.reflect.*;
+import java.util.*;
+//import java.util.List;
+
+import org.apache.tools.ant.*;
+import org.apache.tools.ant.Project;
+import org.aspectj.bridge.*;
+import org.aspectj.tools.ant.taskdefs.AjcTask;
+import org.aspectj.util.*;
+import org.aspectj.util.FileUtil;
+
+/**
+ * Drive tests using the Ant taskdef.
+ * The non-incremental case is quite easy to implement,
+ * but incremental compiles require running the compiler
+ * in another thread using an incremental tag file.
+ * This is imprecise because it assumes
+ * incremental compiles are complete
+ * when messages stop coming from the compiler.
+ * Also, the client should call quit() when done compiling
+ * to halt the process.
+ * XXX build up ICommand with quit (and done-with-last-invocation?)
+ * to avoid the kludge workarounds
+ */
+public class AjcTaskCompileCommand implements ICommand {
+ /**
+ * 20 seconds of quiet in message holder
+ * before we assume incremental compile is done
+ */
+ public static int COMPILE_SECONDS_WITHOUT_MESSAGES = 20;
+
+ /** 5 minutes maximum time to wait for a compile to complete */
+ public static int COMPILE_MAX_SECONDS = 300;
+
+ /**
+ * Wait until this holder has not changed the number of messages
+ * in secs seconds, as a weak form of determining when the
+ * compile has completed.
+ * XXX implement "compile-complete" message instead.
+ * @param holder the IMessageHolder to wait for
+ * @param seconds the number of seconds that the messages should be the same
+ * @param timeoutSeconds the int number of seconds after which to time out
+ * @return true if the messages quiesced before the timeout
+ * false if parameters are 0 or negative or if
+ * seconds => timeoutSeconds
+ */
+ static boolean waitUntilMessagesQuiet(
+ IMessageHolder holder,
+ int seconds,
+ int timeoutSeconds) {
+ LangUtil.throwIaxIfNull(holder, "holder");
+ if ((0 >= timeoutSeconds) || (0 >= seconds)
+ || (timeoutSeconds <= seconds)) {
+ return false;
+ }
+ long curTime = System.currentTimeMillis();
+ final long timeout = curTime + (timeoutSeconds*1000);
+// final Thread thread = Thread.currentThread();
+ final long targetQuietTime = 1000 * seconds;
+ int numMessages = holder.numMessages(null, true);
+ long numMessagesTime = curTime;
+ // check for new messages every .5 to 3 seconds
+ final long checkInterval;
+ {
+ long interval = seconds / 10;
+ if (interval > 3000) {
+ interval = 3000;
+ } else if (interval < 200) {
+ interval = 500;
+ }
+ checkInterval = interval;
+ }
+ while ((curTime < timeout)
+ && (curTime < (numMessagesTime + targetQuietTime))) {
+ // delay until next check
+ long nextCheck = curTime + checkInterval;
+ while (nextCheck > curTime) {
+ try {
+ Thread.sleep(nextCheck - curTime);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ curTime = System.currentTimeMillis();
+ }
+ int newNumMessages = holder.numMessages(null, true);
+ if (newNumMessages != numMessages) {
+ numMessages = newNumMessages;
+ numMessagesTime = curTime;
+ }
+ }
+ return (curTime >= (numMessagesTime + targetQuietTime));
+ }
+
+ // single-threaded driver
+ MessageHandler messages = new MessageHandler();
+ AjcTask ajcTask;
+ File incrementalFile;
+ Thread incrementalCompileThread;
+
+ /**
+ * Stop waiting for any further incremental compiles.
+ * Safe to call in non-incremental modes.
+ */
+ public void quit() { // XXX requires downcast from ICommand, need validator,IMessageHandler interface
+ AjcTask task = ajcTask;
+ if (null != task) {
+ task.quit(); // signal task to quit, thread to die
+ ajcTask = null; // XXX need for cleanup?
+ }
+ updateIncrementalFile(false, true);
+ Thread thread = incrementalCompileThread;
+ if (null != thread) {
+ if (thread.isAlive()) {
+ try {
+ thread.join(3000);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ if (thread.isAlive()) {
+ String s = "WARNING: abandoning undying thread ";
+ System.err.println(s + thread.getName());
+ }
+ }
+ incrementalCompileThread = null;
+ }
+ }
+
+ // --------- ICommand interface
+ public boolean runCommand(String[] args, IMessageHandler handler) {
+ return (makeAjcTask(args, handler)
+ && doCommand(handler, false));
+ }
+
+ /**
+ * Fails if called before runCommand or if
+ * not in incremental mode or if the command
+ * included an incremental file entry.
+ * @return
+ */
+ public boolean repeatCommand(IMessageHandler handler) {
+ return doCommand(handler, true);
+ }
+
+ protected boolean doCommand(IMessageHandler handler, boolean repeat) {
+ messages.clearMessages();
+ if (null == ajcTask) {
+ MessageUtil.fail(messages, "ajcTask null - repeat without do");
+ }
+ try {
+ // normal, non-incremental case
+ if (!repeat && (null == incrementalFile)) {
+ ajcTask.execute();
+ // rest is for incremental mode
+ } else if (null == incrementalFile) {
+ MessageUtil.fail(messages, "incremental mode not specified");
+ } else if (!updateIncrementalFile(false, false)) {
+ MessageUtil.fail(messages, "can't update incremental file");
+ } else if (!repeat) { // first incremental compile
+ incrementalCompileThread = new Thread(
+ new Runnable() {
+ public void run() {
+ ajcTask.execute();
+ }
+ }, "AjcTaskCompileCommand-incremental");
+ incrementalCompileThread.start();
+ waitUntilMessagesQuiet(messages, 10, 180);
+ } else {
+ Thread thread = incrementalCompileThread;
+ if (null == thread) {
+ MessageUtil.fail(messages, "incremental process stopped");
+ } else if (!thread.isAlive()) {
+ MessageUtil.fail(messages, "incremental process dead");
+ } else {
+ waitUntilMessagesQuiet(messages, 10, 180);
+ }
+ }
+ } catch (BuildException e) {
+ Throwable t = e.getCause();
+ while (t instanceof BuildException) {
+ t = ((BuildException) t).getCause();
+ }
+ if (null == t) {
+ t = e;
+ }
+ MessageUtil.abort(messages, "BuildException " + e.getMessage(), t);
+ } finally {
+ MessageUtil.handleAll(handler, messages, false);
+ }
+ return (0 == messages.numMessages(IMessage.ERROR, true));
+ }
+
+
+ protected boolean makeAjcTask(String[] args, IMessageHandler handler) {
+ ajcTask = null;
+ incrementalFile = null;
+ AjcTask result = null;
+ try {
+ result = new AjcTask();
+ Project project = new Project();
+ project.setName("AjcTaskCompileCommand");
+ result.setProject(project);
+ result.setMessageHolder(messages);
+ // XXX argh - have to strip out "-incremental"
+ // because tools.ajc.Main privileges it over tagfile
+ ArrayList newArgs = new ArrayList();
+ boolean incremental = false;
+ for (int i = 0; i < args.length; i++) {
+ if ("-incremental".equals(args[i])) {
+ incremental = true;
+ } else if ("-XincrementalFile".equals(args[i])) {
+ // CommandController.TAG_FILE_OPTION = "-XincrementalFile";
+ incremental = true;
+ i++;
+ } else {
+ newArgs.add(args[i]);
+ }
+ }
+ if (incremental) {
+ args = (String[]) newArgs.toArray(new String[0]);
+ }
+
+ result.readArguments(args);
+
+ if (incremental || result.isInIncrementalMode()) {
+ // these should be impossible...
+ if (result.isInIncrementalFileMode()) {
+ String m = "incremental file set in command";
+ MessageUtil.fail(handler, m);
+ return false;
+ } else if (null != incrementalFile) {
+ String m = "incremental file set already";
+ MessageUtil.fail(handler, m);
+ return false;
+ }
+ String prefix = "AjcTaskCompileCommand_makeAjcTask";
+ File tmpDir = FileUtil.getTempDir(prefix);
+ incrementalFile = new File(tmpDir, "tagfile.tmp");
+ boolean create = true;
+ boolean delete = false;
+ updateIncrementalFile(create, delete);
+ result.setTagFile(incrementalFile);
+ }
+ // do not throw BuildException on error
+ result.setFailonerror(false);
+ ajcTask = result;
+ } catch (BuildException e) {
+ MessageUtil.abort(handler,"setting up AjcTask", e);
+ }
+ return (null != ajcTask);
+ }
+
+ protected boolean updateIncrementalFile(boolean create, boolean delete) {
+ File file = incrementalFile;
+ if (delete) {
+ try {
+ return (null == file)
+ || !file.exists()
+ || file.delete();
+ } finally {
+ incrementalFile = null;
+ }
+ }
+ if (null == file) {
+ return false;
+ }
+ if (file.exists()) {
+ return file.setLastModified(System.currentTimeMillis());
+ } else {
+ try {
+ return create && file.createNewFile();
+ } catch (IOException e) { // XXX warn in verbose mode?
+ return false;
+ }
+ }
+ }
+
+}
+
diff --git a/testing/src/test/java/org/aspectj/testing/taskdefs/TaskdefTests.java b/testing/src/test/java/org/aspectj/testing/taskdefs/TaskdefTests.java
deleted file mode 100644
index 5a86f8558..000000000
--- a/testing/src/test/java/org/aspectj/testing/taskdefs/TaskdefTests.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/* *******************************************************************
- * Copyright (c) 2003 Contributors.
- * All rights reserved.
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- * Wes Isberg initial implementation
- * ******************************************************************/
-
-package org.aspectj.testing.taskdefs;
-
-import junit.framework.*;
-
-public class TaskdefTests extends TestCase {
-
- public static Test suite() {
- TestSuite suite = new TestSuite(TaskdefTests.class.getName());
- //$JUnit-BEGIN$
- suite.addTestSuite(AjcTaskCompileCommandTest.class);
- //$JUnit-END$
- return suite;
- }
-
- public TaskdefTests(String name) { super(name); }
-
-}
diff --git a/testing/src/test/java/org/aspectj/testing/util/AccumulatingFileFilter.java b/testing/src/test/java/org/aspectj/testing/util/AccumulatingFileFilter.java
new file mode 100644
index 000000000..e01f71874
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/util/BridgeUtil.java b/testing/src/test/java/org/aspectj/testing/util/BridgeUtil.java
new file mode 100644
index 000000000..f06e93850
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/util/BridgeUtil.java
@@ -0,0 +1,517 @@
+/* *******************************************************************
+ * 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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.util;
+
+import java.io.File;
+import java.util.Comparator;
+
+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;
+
+/**
+ *
+ */
+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(UtilLineReader 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.getSourceLocation(); // XXX implement
+ }
+
+
+ public static class Comparators {
+ /**
+ * Compare based solely on null-inequality:
+ * -1 if one is not null and two is null,
+ * 1 if one is null and two is not null,
+ * 0 otherwise.
+ */
+ static int compareNull(Object one, Object two) {
+ return (null == one
+ ? (null == two ? 0 : 1)
+ : (null == two ? -1 : 0));
+ }
+
+ /**
+ * Soft comparison of String returns 0 if either is empty
+ * or a substring of the other, and the case-insensitive
+ * ordering otherwise.
+ * @param lhs_s
+ * @param rhs_s
+ * @return
+ */
+ static int compareStringsSoftly(String lhs_s, String rhs_s) {
+ if (LangUtil.isEmpty(lhs_s)
+ || LangUtil.isEmpty(rhs_s)) {
+ return 0;
+ }
+ if ((-1 != lhs_s.indexOf(rhs_s))
+ || (-1 != rhs_s.indexOf(lhs_s))) {
+ return 0;
+ }
+ return String.CASE_INSENSITIVE_ORDER.compare(lhs_s, rhs_s);
+ }
+
+ /**
+ * This returns 0 if one file path is a suffix of the other
+ * or a case-insensitive string comparison otherwise.
+ * WARNING: it returns 0 if either file is
+ * ISourceLocation.NO_FILE to permit tests to
+ * not specify file paths.
+ *
+ * Use only for sorts, not to maintain maps.
+ */
+ public static final Comparator WEAK_File = new Comparator() {
+ public int compare(Object o1, Object o2) {
+ if ((o1 == o2)
+ || (o1 == ISourceLocation.NO_FILE)
+ || (o2 == ISourceLocation.NO_FILE) ) {
+ return 0;
+ }
+ int result = compareNull(o1, o2);
+ if (0 != result) {
+ return result;
+ }
+ File one = (File) o1;
+ File two = (File) o2;
+ String s1 = one.getPath();
+ String s2 = two.getPath();
+ // check if normalize needed
+ if (s1.endsWith(s2) || s2.endsWith(s1)) {
+ return 0;
+ }
+ s1 = FileUtil.weakNormalize(s1);
+ s2 = FileUtil.weakNormalize(s2);
+ if (s1.endsWith(s2) || s2.endsWith(s1)) {
+ return 0;
+ }
+ return String.CASE_INSENSITIVE_ORDER.compare(s1, s2);
+
+ }
+ };
+ /**
+ * Ordering only uses line number.
+ * Use only for sorts, not to maintain maps.
+ */
+ public static final Comparator WEAK_ISourceLocation = new Comparator() {
+ public int compare(Object o1, Object o2) {
+ if (o1 == o2) {
+ return 0;
+ }
+ int result = compareNull(o1, o2);
+ if (0 != result) {
+ return result;
+ }
+ ISourceLocation one = (ISourceLocation) o1;
+ ISourceLocation two = (ISourceLocation) o2;
+ int i1 = one.getLine();
+ int i2 = two.getLine();
+ return i1 - i2;
+ }
+ };
+
+ /**
+ * Like WEAK_ISourceLocation, except it also
+ * uses WEAK_FILE on the sourceFile.
+ * Use only for sorts, not to maintain maps.
+ */
+ public static final Comparator MEDIUM_ISourceLocation = new Comparator() {
+ public int compare(Object o1, Object o2) {
+ int result = WEAK_ISourceLocation.compare(o1, o2);
+ if (0 != result) {
+ return result;
+ }
+ ISourceLocation one = (ISourceLocation) o1;
+ ISourceLocation two = (ISourceLocation) o2;
+ result = compareNull(one, two);
+ if (0 != result) { // one but not other is null
+ return result;
+ }
+ if (null == one) { // both null
+ return 0;
+ }
+ // neither null
+ return WEAK_File.compare(one.getSourceFile(), two.getSourceFile());
+ }
+ };
+
+ /**
+ * Ordering uses kind and weak source location,
+ * and ignores 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) {
+ if (o1 == o2) {
+ return 0;
+ }
+ int result = compareNull(o1, o2);
+ if (0 != result) {
+ return result;
+ }
+ IMessage one = (IMessage) o1;
+ IMessage two = (IMessage) o2;
+ IMessage.Kind kind1 = one.getKind();
+ IMessage.Kind kind2= two.getKind();
+ result = IMessage.Kind.COMPARATOR.compare(kind1, kind2);
+ if (0 != result) {
+ return result;
+ }
+ ISourceLocation sl1 = one.getSourceLocation();
+ ISourceLocation sl2 = two.getSourceLocation();
+ return WEAK_ISourceLocation.compare(sl1, sl2);
+ }
+ };
+
+ /**
+ * Ordering uses line and weak filename and message
+ * (message matches if either is a substring of the other,
+ * or if either is empty, i.e., none specified).
+ * so use only for sorts, not to maintain maps
+ */
+ public static final Comparator MEDIUM_IMessage = new Comparator() {
+ public int compare(Object o1, Object o2) {
+ int result = WEAK_IMessage.compare(o1, o2);
+ if (0 != result) {
+ return result;
+ }
+ IMessage rhs_m= (IMessage) o1;
+ IMessage lhs_m = (IMessage) o2;
+ ISourceLocation rhs_sl = rhs_m.getSourceLocation();
+ ISourceLocation lhs_sl = lhs_m.getSourceLocation();
+ result = MEDIUM_ISourceLocation.compare(lhs_sl, rhs_sl);
+ if (0 != result) {
+ return result;
+ }
+ String lhs_s =lhs_m.getMessage();
+ String rhs_s = rhs_m.getMessage();
+ return compareStringsSoftly(lhs_s, rhs_s);
+ }
+ };
+}
+ 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
+ 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/test/java/org/aspectj/testing/util/CollectorFileFilter.java b/testing/src/test/java/org/aspectj/testing/util/CollectorFileFilter.java
new file mode 100644
index 000000000..7c63c3b6c
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/util/Diffs.java b/testing/src/test/java/org/aspectj/testing/util/Diffs.java
new file mode 100644
index 000000000..a6edf0d87
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/util/Diffs.java
@@ -0,0 +1,547 @@
+/* *******************************************************************
+ * 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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.util;
+
+import java.io.File;
+import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+import org.aspectj.bridge.IMessage;
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.bridge.ISourceLocation;
+import org.aspectj.bridge.MessageUtil;
+import org.aspectj.util.FileUtil;
+import org.aspectj.util.LangUtil;
+
+/**
+ * Result struct for expected/actual diffs for Collection
+ */
+public class Diffs {
+
+ /**
+ * Compare IMessage.Kind based on kind priority.
+ */
+ public static final Comparator KIND_PRIORITY = new Comparator() {
+ /**
+ * Compare IMessage.Kind based on kind priority.
+ * @throws NullPointerException if anything is null
+ */
+ public int compare(Object lhs, Object rhs) {
+ return ((IMessage.Kind) lhs).compareTo((IMessage.Kind) rhs);
+ }
+ };
+ /**
+ * Sort ISourceLocation based on line, file path.
+ */
+ public static final Comparator SORT_SOURCELOC = new Comparator() {
+ /**
+ * Compare ISourceLocation based on line, file path.
+ * @throws NullPointerException if anything is null
+ */
+ public int compare(Object lhs, Object rhs) {
+ ISourceLocation l = (ISourceLocation) lhs;
+ ISourceLocation r = (ISourceLocation) rhs;
+ int result = getLine(l) - getLine(r);
+ if (0 != result) {
+ return result;
+ }
+ String lp = getSourceFile(l).getPath();
+ String rp = getSourceFile(r).getPath();
+ return lp.compareTo(rp);
+ }
+ };
+
+ /**
+ * Compare IMessages based on kind and source location line (only).
+ */
+ public static final Comparator MESSAGE_LINEKIND = new Comparator() {
+ /**
+ * Compare IMessages based on kind and source location line (only).
+ * @throws NullPointerException if anything is null
+ */
+ public int compare(Object lhs, Object rhs) {
+ IMessage lm = (IMessage) lhs;
+ IMessage rm = (IMessage) rhs;
+ ISourceLocation ls = (lm == null ? null : lm.getSourceLocation());
+ ISourceLocation rs = (rm == null ? null : rm.getSourceLocation());
+ int left = (ls == null ? -1 : ls.getLine());
+ int right = (rs == null ? -1 : rs.getLine());
+ int result = left - right;
+ if (0 == result) {
+ result = lm.getKind().compareTo(rm.getKind());
+ }
+ return result;
+ }
+ };
+ public static final Filter ACCEPT_ALL = new Filter() {
+ public boolean accept(Object o) {
+ return true;
+ }
+ };
+ // // XXX List -> Collection b/c comparator orders
+ // public static final Diffs NONE
+ // = new Diffs("NONE", Collections.EMPTY_LIST, Collections.EMPTY_LIST);
+
+ public static Diffs makeDiffs(
+ String label,
+ List expected,
+ List actual,
+ Comparator comparator) {
+ return makeDiffs(
+ label,
+ expected,
+ actual,
+ comparator,
+ ACCEPT_ALL,
+ ACCEPT_ALL);
+ }
+
+ public static Diffs makeDiffs(
+ String label,
+ IMessage[] expected,
+ IMessage[] actual) {
+ return makeDiffs(label, expected, actual, null, null);
+ }
+
+ private static int getLine(ISourceLocation loc) {
+ int result = -1;
+ if (null != loc) {
+ result = loc.getLine();
+ }
+ return result;
+ }
+ private static int getLine(IMessage message) {
+ int result = -1;
+ if ((null != message)) {
+ result = getLine(message.getSourceLocation());
+ }
+ return result;
+ }
+
+ private static File getSourceFile(ISourceLocation loc) {
+ File result = ISourceLocation.NO_FILE;
+ if (null != loc) {
+ result = loc.getSourceFile();
+ }
+ return result;
+ }
+
+ public static Diffs makeDiffs(
+ String label,
+ IMessage[] expected,
+ IMessage[] actual,
+ IMessage.Kind[] ignoreExpectedKinds,
+ IMessage.Kind[] ignoreActualKinds) {
+ ArrayList exp = getExcept(expected, ignoreExpectedKinds);
+ ArrayList act = getExcept(actual, ignoreActualKinds);
+
+ ArrayList missing = new ArrayList();
+ List unexpected = new ArrayList();
+
+ if (LangUtil.isEmpty(expected)) {
+ unexpected.addAll(act);
+ } else if (LangUtil.isEmpty(actual)) {
+ missing.addAll(exp);
+ } else {
+ ListIterator expectedIterator = exp.listIterator();
+ int lastLine = Integer.MIN_VALUE + 1;
+ ArrayList expectedFound = new ArrayList();
+ ArrayList expectedForLine = new ArrayList();
+ for (ListIterator iter = act.listIterator(); iter.hasNext();) {
+ IMessage actualMessage = (IMessage) iter.next();
+ int actualLine = getLine(actualMessage);
+ if (actualLine != lastLine) {
+ // new line - get all messages expected for it
+ if (lastLine > actualLine) {
+ throw new Error("sort error");
+ }
+ lastLine = actualLine;
+ expectedForLine.clear();
+ while (expectedIterator.hasNext()) {
+ IMessage curExpected =
+ (IMessage) expectedIterator.next();
+ int curExpectedLine = getLine(curExpected);
+ if (actualLine == curExpectedLine) {
+ expectedForLine.add(curExpected);
+ } else {
+ expectedIterator.previous();
+ break;
+ }
+ }
+ }
+ // now check actual against all expected on that line
+ boolean found = false;
+ IMessage expectedMessage = null;
+ for (Iterator iterator = expectedForLine.iterator();
+ !found && iterator.hasNext();
+ ) {
+ expectedMessage = (IMessage) iterator.next();
+ found = expectingMessage(expectedMessage, actualMessage);
+ }
+ if (found) {
+ iter.remove();
+ if (expectedFound.contains(expectedMessage)) {
+ // XXX warn: expected message matched two actual
+ } else {
+ expectedFound.add(expectedMessage);
+ }
+ } else {
+ // unexpected: any actual result not found
+ unexpected.add(actualMessage);
+ }
+ }
+ // missing: all expected results not found
+ exp.removeAll(expectedFound);
+ missing.addAll(exp);
+ }
+ return new Diffs(label, missing, unexpected);
+ }
+
+ public static Diffs makeDiffs(
+ String label,
+ List expected,
+ List actual,
+ Comparator comparator,
+ Filter missingFilter,
+ Filter unexpectedFilter) {
+ label = label.trim();
+ if (null == label) {
+ label = ": ";
+ } else if (!label.endsWith(":")) {
+ label += ": ";
+ }
+ final String thisLabel = " " + label;
+ ArrayList miss = new ArrayList();
+ ArrayList unexpect = new ArrayList();
+
+ org.aspectj.testing.util.LangUtil.makeSoftDiffs(
+ expected,
+ actual,
+ miss,
+ unexpect,
+ comparator);
+ if (null != missingFilter) {
+ for (ListIterator iter = miss.listIterator(); iter.hasNext();) {
+ if (!missingFilter.accept(iter.next())) {
+ iter.remove();
+ }
+ }
+ }
+ if (null != unexpectedFilter) {
+ for (ListIterator iter = unexpect.listIterator();
+ iter.hasNext();
+ ) {
+ if (!unexpectedFilter.accept(iter.next())) {
+ iter.remove();
+ }
+ }
+ }
+ return new Diffs(thisLabel, miss, unexpect);
+ }
+
+ // /**
+ // * Shift over elements in sink if they are of one of the specified kinds.
+ // * @param sink the IMessage[] to shift elements from
+ // * @param kinds
+ // * @return length of sink after shifting
+ // * (same as input length if nothing shifted)
+ // */
+ // public static int removeKinds(IMessage[] sink, IMessage.Kind[] kinds) {
+ // if (LangUtil.isEmpty(kinds)) {
+ // return sink.length;
+ // } else if (LangUtil.isEmpty(sink)) {
+ // return 0;
+ // }
+ // int from = -1;
+ // int to = -1;
+ // for (int j = 0; j < sink.length; j++) {
+ // from++;
+ // if (null == sink[j]) {
+ // continue;
+ // }
+ // boolean remove = false;
+ // for (int i = 0; !remove && (i < kinds.length); i++) {
+ // IMessage.Kind kind = kinds[i];
+ // if (null == kind) {
+ // continue;
+ // }
+ // if (0 == kind.compareTo(sink[j].getKind())) {
+ // remove = true;
+ // }
+ // }
+ // if (!remove) {
+ // to++;
+ // if (to != from) {
+ // sink[to] = sink[from];
+ // }
+ // }
+ // }
+ // return to+1;
+ // }
+
+ /**
+ * @param expected the File from the expected source location
+ * @param actual the File from the actual source location
+ * @return true if exp is ISourceLocation.NO_FILE
+ * or exp path is a suffix of the actual path
+ * (after using FileUtil.weakNormalize(..) on both)
+ */
+ static boolean expectingFile(File expected, File actual) {
+ if (null == expected) {
+ return (null == actual);
+ } else if (null == actual) {
+ return false;
+ }
+ if (expected != ISourceLocation.NO_FILE) {
+ String expPath = FileUtil.weakNormalize(expected.getPath());
+ String actPath = FileUtil.weakNormalize(actual.getPath());
+ if (!actPath.endsWith(expPath)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Soft comparison for expected message will not check a corresponding
+ * element in the actual message unless defined in the expected message.
+ * <pre>
+ * message
+ * kind must match (constant/priority)
+ * message only requires substring
+ * thrown ignored
+ * column ignored
+ * endline ignored
+ * details only requires substring
+ * sourceLocation
+ * line must match, unless expected < 0
+ * file ignored if ISourceLocation.NOFILE
+ * matches if expected is a suffix of actual
+ * after changing any \ to /
+ * extraSourceLocation[]
+ * if any are defined in expected, then there
+ * must be exactly the actual elements as are
+ * defined in expected (so it is an error to
+ * not define all if you define any)
+ * <pre>
+ * @param expected
+ * @param actual
+ * @return true if we are expecting the line, kind, file, message,
+ * details, and any extra source locations.
+ * (ignores column/endline, thrown) XXX
+ */
+ static boolean expectingMessage(IMessage expected, IMessage actual) {
+ if (null == expected) {
+ return (null == actual);
+ } else if (null == actual) {
+ return false;
+ }
+ if (0 != expected.getKind().compareTo(actual.getKind())) {
+ return false;
+ }
+ if (!expectingSourceLocation(expected.getSourceLocation(),
+ actual.getSourceLocation())) {
+ return false;
+ }
+ if (!expectingText(expected.getMessage(), actual.getMessage())) {
+ return false;
+ }
+ if (!expectingText(expected.getDetails(), actual.getDetails())) {
+ return false;
+ }
+ ISourceLocation[] esl =
+ (ISourceLocation[]) expected.getExtraSourceLocations().toArray(
+ new ISourceLocation[0]);
+ ISourceLocation[] asl =
+ (ISourceLocation[]) actual.getExtraSourceLocations().toArray(
+ new ISourceLocation[0]);
+
+ Arrays.sort(esl, SORT_SOURCELOC);
+ Arrays.sort(asl, SORT_SOURCELOC);
+ if (!expectingSourceLocations(esl, asl)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * This returns true if no ISourceLocation are specified
+ * (i.e., it ignored any extra source locations if no expectations stated).
+ * XXX need const like NO_FILE.
+ * @param expected the sorted ISourceLocation[] expected
+ * @param expected the actual sorted ISourceLocation[]
+ * @return true if any expected element is expected by the corresponding actual element.
+ */
+ static boolean expectingSourceLocations(
+ ISourceLocation[] expected,
+ ISourceLocation[] actual) {
+ if (LangUtil.isEmpty(expected)) {
+ return true;
+ } else if (LangUtil.isEmpty(actual)) {
+ return false;
+ } else if (actual.length != expected.length) {
+ return false;
+ }
+ for (int i = 0; i < actual.length; i++) {
+ if (!expectingSourceLocation(expected[i], actual[i])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @param expected
+ * @param actual
+ * @return true if any expected line/file matches the actual line/file,
+ * accepting a substring as a file match
+ */
+ static boolean expectingSourceLocation(
+ ISourceLocation expected,
+ ISourceLocation actual) {
+ int eline = getLine(expected);
+ int aline = getLine(actual);
+ if ((-1 < eline) && (eline != aline)) {
+ return false;
+ }
+ if (!expectingFile(getSourceFile(expected), getSourceFile(actual))) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @param expected the String in the expected message
+ * @param actual the String in the actual message
+ * @return true if both are null or actual contains expected
+ */
+ static boolean expectingText(String expected, String actual) {
+ if (null == expected) {
+ return true; // no expectations
+ } else if (null == actual) {
+ return false; // expected something
+ } else {
+ return (-1 != actual.indexOf(expected));
+ }
+ }
+
+ private static ArrayList getExcept(
+ IMessage[] source,
+ IMessage.Kind[] skip) {
+ ArrayList sink = new ArrayList();
+ if (LangUtil.isEmpty(source)) {
+ return sink;
+ }
+
+ if (LangUtil.isEmpty(skip)) {
+ sink.addAll(Arrays.asList(source));
+ Collections.sort(sink, MESSAGE_LINEKIND);
+ return sink;
+ }
+ for (int i = 0; i < source.length; i++) {
+ IMessage message = source[i];
+ IMessage.Kind mkind = message.getKind();
+ boolean skipping = false;
+ for (int j = 0; !skipping && (j < skip.length); j++) {
+ if (0 == mkind.compareTo(skip[j])) {
+ skipping = true;
+ }
+ }
+ if (!skipping) {
+ sink.add(message);
+ }
+ }
+ Collections.sort(sink, MESSAGE_LINEKIND);
+ return sink;
+ }
+
+ private static List harden(List list) {
+ return (
+ LangUtil.isEmpty(list)
+ ? Collections.EMPTY_LIST
+ : Collections.unmodifiableList(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;
+
+ /**
+ * Struct-constructor stores these values,
+ * wrapping the lists as unmodifiable.
+ * @param label the String label for these diffs
+ * @param missing the List of missing elements
+ * @param unexpected the List of unexpected elements
+ */
+ public Diffs(String label, List missing, List unexpected) {
+ this.label = label;
+ this.missing = harden(missing);
+ this.unexpected = harden(unexpected);
+ 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()
+ + ")";
+ }
+ public static interface Filter {
+ /** @return true to keep input in list of messages */
+ boolean accept(Object input);
+ }
+}
diff --git a/testing/src/test/java/org/aspectj/testing/util/FileUtil.java b/testing/src/test/java/org/aspectj/testing/util/FileUtil.java
new file mode 100644
index 000000000..db77cbd10
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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 Diffs.makeDiffs(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 enu = zipfile.entries();
+ while (enu.hasMoreElements()) {
+ ZipEntry entry = (ZipEntry) enu.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 enu = zip.entries();
+ while (enu.hasMoreElements()) {
+ results.add(renderZipEntry(zipfile, (ZipEntry) enu.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/test/java/org/aspectj/testing/util/IntRange.java b/testing/src/test/java/org/aspectj/testing/util/IntRange.java
new file mode 100644
index 000000000..1f44f69bd
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/util/IntValidator.java b/testing/src/test/java/org/aspectj/testing/util/IntValidator.java
new file mode 100644
index 000000000..e3697d1aa
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/util/IteratorWrapper.java b/testing/src/test/java/org/aspectj/testing/util/IteratorWrapper.java
new file mode 100644
index 000000000..5b7c13af9
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/util/LangUtil.java b/testing/src/test/java/org/aspectj/testing/util/LangUtil.java
new file mode 100644
index 000000000..2baf81c48
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/util/LangUtil.java
@@ -0,0 +1,1271 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2000 Xerox Corporation.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.util;
+
+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;
+
+import org.aspectj.bridge.AbortException;
+import org.aspectj.bridge.IMessage;
+
+
+/**
+ * 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);
+ }
+ }
+
+ /**
+ * Get indexes of any invalid entries in array.
+ * @param ra the Object[] entries to check
+ * (if null, this returns new int[] { -1 })
+ * @param superType the Class, if any, to verify that
+ * any entries are assignable.
+ * @return null if all entries are non-null, assignable to superType
+ * or comma-delimited error String, with components
+ * <code>"[#] {null || not {superType}"</code>,
+ * e.g., "[3] null, [5] not String"
+ */
+ public static String invalidComponents(Object[] ra, Class superType) {
+ if (null == ra) {
+ return "null input array";
+ } else if (0 == ra.length) {
+ return null;
+ }
+ StringBuffer result = new StringBuffer();
+ final String cname = LangUtil.unqualifiedClassName(superType);
+// int index = 0;
+ for (int i = 0; i < ra.length; i++) {
+ if (null == ra[i]) {
+ result.append(", [" + i + "] null");
+ } else if ((null != superType)
+ && !superType.isAssignableFrom(ra[i].getClass())) {
+ result.append(", [" + i + "] not " + cname);
+ }
+ }
+ if (0 == result.length()) {
+ return null;
+ } else {
+ return result.toString().substring(2);
+ }
+ }
+
+ /** @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()));
+ }
+
+
+ /**
+ * Throw IllegalArgumentException if any component in input array
+ * is null or (if superType is not null) not assignable to superType.
+ * The exception message takes the form
+ * <code>{name} invalid entries: {invalidEntriesResult}</code>
+ * @throws IllegalArgumentException if any components bad
+ * @see #invalidComponents(Object[], Class)
+ */
+ public static final void throwIaxIfComponentsBad(
+ final Object[] input,
+ final String name,
+ final Class superType) {
+ String errs = invalidComponents(input, superType);
+ if (null != errs) {
+ String err = name + " invalid entries: " + errs;
+ throw new IllegalArgumentException(err);
+ }
+ }
+ /**
+ * 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);
+
+ ArrayList actual = new ArrayList();
+ actual.addAll(actualListIn);
+ Collections.sort(actual, comparator);
+ Iterator actualIter = actual.iterator();
+ Object act = null;
+
+ if (missingListOut != null) {
+ missingListOut.addAll(expectedListIn);
+ }
+ if (extraListOut != null) {
+ extraListOut.addAll(actualListIn);
+ }
+
+ // AMC: less efficient, but simplified implementation. Needed since messages can
+ // now match on text content too, and the old algorithm did not cope with two expected
+ // messages on the same line, but with different text content.
+ while (actualIter.hasNext()) {
+ act = actualIter.next();
+ for (Iterator expectedIter = expected.iterator(); expectedIter.hasNext();) {
+ Object exp = expectedIter.next();
+ // if actual matches expected remove actual from extraListOut, and
+ // remove expected from missingListOut
+ int diff = comparator.compare(exp,act);
+ if (diff == 0) {
+ extraListOut.remove(act);
+ missingListOut.remove(exp);
+ } else if (diff > 0) {
+ // since list is sorted, there can be no more matches...
+ break;
+ }
+ }
+ }
+
+// 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/test/java/org/aspectj/testing/util/LineReader.java b/testing/src/test/java/org/aspectj/testing/util/LineReader.java
new file mode 100644
index 000000000..a6af8266a
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/util/LinkCheck.java b/testing/src/test/java/org/aspectj/testing/util/LinkCheck.java
new file mode 100644
index 000000000..3f1ef92a1
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/util/LinkCheck.java
@@ -0,0 +1,546 @@
+/* *******************************************************************
+ * Copyright (c) 2003 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Wes Isberg initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.util;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+import javax.swing.text.MutableAttributeSet;
+import javax.swing.text.html.*;
+import javax.swing.text.html.HTML.Tag;
+
+import org.aspectj.bridge.*;
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.util.LangUtil;
+//import org.aspectj.util.FileUtil;
+
+/**
+ * Quick and dirty link checker.
+ * This checks that links into file: and http: targets work,
+ * and that links out of file: targets work.
+ */
+public class LinkCheck {
+ /*
+ * Known issues:
+ * - main interface only, though API's easily upgraded
+ * - https MalformedUrlExceptions on redirect
+ * - Swing won't quit without System.exit
+ * - single-threaded
+ */
+ static final URL COMMAND_LINE;
+ static {
+ URL commandLine = null;
+ try {
+ commandLine = new URL("file://commandLine");
+ } catch (Throwable t) {
+ }
+ COMMAND_LINE = commandLine;
+ }
+
+ /** @param args file {-logFile {file} | -printInfo } */
+ public static void main(String[] args) {
+ final String syntax = "java "
+ + LinkCheck.class.getName()
+ + " file {-log <file> | -printInfo}..";
+ if ((null == args) || (0 >= args.length)) {
+ System.err.println(syntax);
+ System.exit(1);
+ }
+ final String startingURL = "file:///" + args[0].replace('\\', '/');
+ String logFile = null;
+ boolean printInfo = false;
+ for (int i = 1; i < args.length; i++) {
+ if ("-log".equals(args[i]) && ((i+1) < args.length)) {
+ logFile = args[++i];
+ } else if ("-printInfo".equals(args[i])) {
+ printInfo = true;
+ } else {
+ System.err.println(syntax);
+ System.exit(1);
+ }
+ }
+ final boolean useSystemOut = (null == logFile);
+ final MessageHandler mh;
+ final OutputStream out;
+ if (useSystemOut) {
+ mh = new MessageHandler();
+ out = null;
+ } else {
+
+ try {
+ out = new FileOutputStream(logFile);
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ return;
+ }
+ final PrintStream ps = new PrintStream(out, true);
+ final boolean printAll = printInfo;
+ mh = new MessageHandler() {
+ public boolean handleMessage(IMessage message) {
+ if (printAll || !message.isInfo()) {
+ ps.println(message.toString());
+ }
+ return super.handleMessage(message);
+ }
+
+ };
+ }
+ Link.Check exists
+ = Link.getProtocolChecker(new String[] {"file", "http"});
+ Link.Check contents
+ = Link.getProtocolChecker(new String[] {"file"});
+ LinkCheck me = new LinkCheck(mh, exists, contents);
+ me.addLinkToCheck(COMMAND_LINE, startingURL); // pwd as base?
+ try {
+ String label = "checking URLs from " + startingURL;
+ if (useSystemOut) {
+ System.out.println(label);
+ }
+ MessageUtil.info("START " + label);
+ long start = System.currentTimeMillis();
+ me.run();
+ long duration = (System.currentTimeMillis() - start)/1000;
+ long numChecked = me.checkedUrls.size();
+ if (numChecked > 0) {
+ float cps = (float) duration / (float) numChecked;
+ StringBuffer sb = new StringBuffer();
+ sb.append("DONE. Checked " + numChecked);
+ sb.append(" URL's in " + duration);
+ sb.append(" seconds (" + cps);
+ sb.append(" seconds per URL).");
+ MessageUtil.info("END " + label + ": " + sb);
+ if (useSystemOut) {
+ System.out.println(sb.toString());
+ }
+ }
+ MessageUtil.info(MessageUtil.renderCounts(mh));
+ try {
+ if (null != out) {
+ out.flush();
+ }
+ } catch (IOException e) {
+ // ignore
+ }
+ if (useSystemOut && (null != logFile)) {
+ System.out.println("Find log in " + logFile);
+ }
+ } finally {
+ if (null != out) {
+ try {
+ out.close();
+ } catch (IOException e1) {
+ }
+ }
+ System.exit(mh.numMessages(IMessage.ERROR, true)); // XXX dumb swing
+ }
+ }
+
+// private static boolean isCheckedFileType(URL url) {
+// if (null == url) {
+// return false;
+// }
+// String file = url.getFile();
+// return !FileUtil.hasZipSuffix(file)
+// && !file.endsWith(".pdf");
+// }
+
+ private final Messages messages;
+ private final HTMLEditorKit.Parser parser; // XXX untested - stateful
+ private final ArrayList linksToCheck; // Link
+ private final ArrayList checkedUrls; // String (URL.toString)
+ private final ArrayList validRefs; // String (URL.toString)
+ private final ArrayList refsToCheck; // String (URL.toString)
+
+ private final Link.Check checkExists;
+ private final Link.Check checkContents;
+
+ public LinkCheck(IMessageHandler handler,
+ Link.Check checkExists,
+ Link.Check checkContents) {
+ LangUtil.throwIaxIfNull(handler, "handler");
+ LangUtil.throwIaxIfNull(checkExists, "checkExists");
+ LangUtil.throwIaxIfNull(checkContents, "checkContents");
+ this.messages = new Messages(handler);
+ linksToCheck = new ArrayList();
+ checkedUrls = new ArrayList();
+ refsToCheck = new ArrayList();
+ validRefs = new ArrayList();
+ parser = new HTMLEditorKit() {
+ public HTMLEditorKit.Parser getParser() {
+ return super.getParser();
+ }
+ }
+ .getParser();
+ this.checkExists = checkExists;
+ this.checkContents = checkContents;
+ }
+
+ public synchronized void addLinkToCheck(URL doc, String link) {
+ URL linkURL = makeURL(doc, link);
+ if (null == linkURL) {
+// messages.addingNullLinkFrom(doc);
+ return;
+ }
+ String linkString = linkURL.toString();
+ if ((null != link) && !checkedUrls.contains(linkString) ) {
+ if (!checkExists.check(linkURL)) {
+ checkedUrls.add(linkString);
+ messages.acceptingUncheckedLink(doc, linkURL);
+ } else {
+ Link toAdd = new Link(doc, linkURL);
+ if (!linksToCheck.contains(toAdd)) { // equals overridden
+ linksToCheck.add(toAdd);
+ }
+ }
+ }
+ }
+
+ public synchronized void run() {
+ ArrayList list = new ArrayList();
+ while (0 < linksToCheck.size()) {
+ messages.checkingLinks(linksToCheck.size());
+ list.clear();
+ list.addAll(linksToCheck);
+ for (Iterator iter = list.iterator(); iter.hasNext();) {
+ final Link link = (Link) iter.next();
+ String urlString = link.url.toString();
+ if (!checkedUrls.contains(urlString)) {
+ checkedUrls.add(urlString);
+ messages.checkingLink(link);
+ checkLink(link);
+ }
+ }
+ linksToCheck.removeAll(list);
+ }
+ // now check that all named references are accounted for
+ for (Iterator iter = refsToCheck.iterator(); iter.hasNext();) {
+ String ref = (String) iter.next();
+ if (!validRefs.contains(ref)) {
+ messages.namedReferenceNotFound(ref);
+ }
+ }
+ }
+
+ /** @return null if link known or if unable to create */
+ private URL makeURL(URL doc, String link) {
+ if (checkedUrls.contains(link)) {
+ return null;
+ }
+ URL result = null;
+ try {
+ result = new URL(link);
+ } catch (MalformedURLException e) {
+ if (null == doc) {
+ messages.malformedUrl(null, link, e);
+ } else {
+ try {
+ URL res = new URL(doc, link);
+ String resultString = res.toString();
+ if (checkedUrls.contains(resultString)) {
+ return null;
+ }
+ result = res;
+ } catch (MalformedURLException me) {
+ messages.malformedUrl(doc, link, me);
+ }
+ }
+ }
+ return result;
+ }
+
+ /** @param link a Link with a url we can handle */
+ private void checkLink(final Link link) {
+ if (handleAsRef(link)) {
+ return;
+ }
+ URL url = link.url;
+ InputStream input = null;
+ try {
+ URLConnection connection = url.openConnection();
+ if (null == connection) {
+ messages.cantOpenConnection(url);
+ return;
+ }
+ // get bad urls to fail on read before skipping by type
+ input = connection.getInputStream();
+ String type = connection.getContentType();
+ if (null == type) {
+ messages.noContentType(link);
+ } else if (!type.toLowerCase().startsWith("text/")) {
+ messages.notTextContentType(link);
+ } else {
+ boolean addingLinks = checkContents.check(url);
+ parser.parse(
+ new InputStreamReader(input),
+ new LinkListener(url, addingLinks), true);
+ }
+ } catch (IOException e) {
+ messages.exceptionReading(link, e);
+ } finally {
+ if (null != input) {
+ try {
+ input.close();
+ } catch (IOException e1) {
+ // ignore
+ }
+ }
+ }
+ }
+
+ /** @return true if link is to an internal ...#name */
+ private boolean handleAsRef(Link link) {
+ String ref = link.url.getRef();
+ if (!LangUtil.isEmpty(ref)) {
+ String refString = link.url.toString(); // XXX canonicalize?
+ if (!refsToCheck.contains(refString)) {
+ refsToCheck.add(refString);
+ // add pseudo-link to force reading of ref'd doc XXX hmm
+ int refLoc = refString.indexOf("#");
+ if (-1 == refLoc) {
+ messages.uncommentedReference(link);
+ } else {
+ refString = refString.substring(0, refLoc);
+ addLinkToCheck(link.doc, refString);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** LinkListener callback */
+ private boolean addKnownNamedAnchor(URL doc, String name) {
+ String namedRef = "#" + name;
+ try {
+ String ref = new URL(doc + namedRef).toString();
+ if (!validRefs.contains(ref)) {
+ validRefs.add(ref);
+ }
+ return true;
+ } catch (MalformedURLException e) {
+ messages.malformedUrl(doc, namedRef, e);
+ return false;
+ }
+ }
+
+ private class Messages {
+ private final IMessageHandler handler;
+ private Messages(IMessageHandler handler) {
+ LangUtil.throwIaxIfNull(handler, "handler");
+ this.handler = handler;
+ }
+
+ private void info(String label, Object more) {
+ MessageUtil.info(handler, label + " " + more);
+ }
+
+ private void fail(String label, Object more, Throwable thrown) {
+ MessageUtil.fail(handler, label + " " + more, thrown);
+ }
+
+ private void uncommentedReference(Link link) {
+ info("uncommentedReference", link); // XXX bug?
+ }
+
+// private void addingNullLinkFrom(URL doc) {
+// info("addingNullLinkFrom", doc);
+// }
+//
+// private void noContentCheck(Link link) {
+// info("noContentCheck", link);
+// }
+
+ private void notTextContentType(Link link) {
+ info("notTextContentType", link);
+ }
+
+ private void noContentType(Link link) {
+ info("noContentType", link);
+ }
+
+ private void checkingLinks(int i) {
+ info("checkingLinks", new Integer(i));
+ }
+
+ private void checkingLink(Link link) {
+ info("checkingLink", link);
+ }
+
+ private void acceptingUncheckedLink(URL doc, URL link) {
+ info("acceptingUncheckedLink", "doc=" + doc + " link=" + link);
+ }
+
+// private void cantHandleRefsYet(Link link) {
+// info("cantHandleRefsYet", link.url);
+// }
+
+ private void namedReferenceNotFound(String ref) {
+ // XXX find all references to this unfound named reference
+ fail("namedReferenceNotFound", ref, null);
+ }
+
+ private void malformedUrl(URL doc, String link, MalformedURLException e) {
+ fail("malformedUrl", "doc=" + doc + " link=" + link, e);
+ }
+
+ private void cantOpenConnection(URL url) {
+ fail("cantOpenConnection", url, null);
+ }
+
+ private void exceptionReading(Link link, IOException e) {
+ // only info if redirect from http to https
+ String m = e.getMessage();
+ if ((m != null)
+ && (-1 != m.indexOf("protocol"))
+ && (-1 != m.indexOf("https"))
+ && "http".equals(link.url.getProtocol())) {
+ info("httpsRedirect", link);
+ return;
+ }
+ fail("exceptionReading", link, e);
+ }
+
+ private void nullLink(URL doc, Tag tag) {
+ // ignore - many tags do not have links
+ }
+
+ private void emptyLink(URL doc, Tag tag) {
+ fail("emptyLink", "doc=" + doc + " tag=" + tag, null);
+ }
+ }
+
+ /**
+ * Register named anchors and add any hrefs to the links to check.
+ */
+ private class LinkListener extends HTMLEditorKit.ParserCallback {
+ private final URL doc;
+ private final boolean addingLinks;
+
+ private LinkListener(URL doc, boolean addingLinks) {
+ this.doc = doc;
+ this.addingLinks = addingLinks;
+ }
+
+ public void handleStartTag(
+ HTML.Tag tag,
+ MutableAttributeSet attributes,
+ int position) {
+ handleSimpleTag(tag, attributes, position);
+ }
+
+ public void handleSimpleTag(
+ HTML.Tag tag,
+ MutableAttributeSet attributes,
+ int position) { // XXX use position to emit context?
+ boolean isNameAnchor = registerIfNamedAnchor(tag, attributes);
+ if (!addingLinks) {
+ return;
+ }
+ Object key = HTML.Tag.FRAME == tag
+ ? HTML.Attribute.SRC
+ : HTML.Attribute.HREF;
+ String link = (String) attributes.getAttribute(key);
+
+ if (null == link) {
+ if (!isNameAnchor) {
+ messages.nullLink(doc, tag);
+ }
+ } else if (0 == link.length()) {
+ if (!isNameAnchor) {
+ messages.emptyLink(doc, tag);
+ }
+ } else {
+ addLinkToCheck(doc, link);
+ }
+ }
+
+ private boolean registerIfNamedAnchor(
+ HTML.Tag tag,
+ MutableAttributeSet attributes) {
+ if (HTML.Tag.A.equals(tag)) {
+ String name
+ = (String) attributes.getAttribute(HTML.Attribute.NAME);
+ if (null != name) {
+ addKnownNamedAnchor(doc, name);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ }
+
+ private static class Link {
+ private static final Check FALSE_CHECKER = new Check() {
+ public boolean check(Link link) { return false; }
+ public boolean check(URL url) { return false; }
+ };
+ private static Check getProtocolChecker(String[] protocols) {
+ final String[] input
+ = (String[]) LangUtil.safeCopy(protocols, protocols);
+ if (0 == input.length) {
+ return FALSE_CHECKER;
+ }
+ return new Check() {
+ final List list = Arrays.asList(input);
+ public boolean check(URL url) {
+ return (null != url) && list.contains(url.getProtocol());
+ }
+ };
+ }
+ private final URL doc;
+ private final URL url;
+ private String toString;
+ private Link(URL doc, URL url) {
+ LangUtil.throwIaxIfNull(doc, "doc");
+ LangUtil.throwIaxIfNull(url, "url");
+ this.doc = doc;
+ this.url = url;
+ }
+ public boolean equals(Object o) {
+ if (null == o) {
+ return false;
+ }
+ if (this == o) {
+ return true;
+ }
+ if (Link.class != o.getClass()) {
+ return false; // exact class
+ }
+ Link other = (Link) o;
+ return doc.equals(other) && url.equals(other);
+ //return toString().equals(o.toString());
+ }
+
+ public int hashCode() { // XXX
+ return doc.hashCode() + (url.hashCode() >> 4);
+// return toString.hashCode();
+ }
+
+ public String toString() {
+ if (null == toString) {
+ toString = url + " linked from " + doc;
+ }
+ return toString;
+ }
+ private static class Check {
+ public boolean check(Link link) {
+ return (null != link) && check(link.url);
+ }
+ public boolean check(URL url) {
+ return (null != url);
+ }
+ }
+ }
+}
diff --git a/testing/src/test/java/org/aspectj/testing/util/Node.java b/testing/src/test/java/org/aspectj/testing/util/Node.java
new file mode 100644
index 000000000..9439e57e4
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/util/NullPrintStream.java b/testing/src/test/java/org/aspectj/testing/util/NullPrintStream.java
new file mode 100644
index 000000000..686c9c8e2
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/util/ObjectChecker.java b/testing/src/test/java/org/aspectj/testing/util/ObjectChecker.java
new file mode 100644
index 000000000..25985ae5e
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/util/ObjectChecker.java
@@ -0,0 +1,51 @@
+/* *******************************************************************
+ * 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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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.NOT_NULL"; }
+ };
+
+ /** @return true if input is 0 Integer or any other non-Integer reference. */
+ public static final ObjectChecker ANY_ZERO = new ObjectChecker() {
+ public boolean isValid(Object input) {
+ if (input instanceof Integer) {
+ return (0 == ((Integer) input).intValue());
+ } else {
+ return true;
+ }
+ }
+ public String toString() { return "ObjectChecker.ANY_ZERO"; }
+ };
+
+ /**
+ * 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/test/java/org/aspectj/testing/util/ProxyPrintStream.java b/testing/src/test/java/org/aspectj/testing/util/ProxyPrintStream.java
new file mode 100644
index 000000000..5c5bbe9de
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/util/RunUtils.java b/testing/src/test/java/org/aspectj/testing/util/RunUtils.java
new file mode 100644
index 000000000..86c29a4a2
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.util;
+
+import java.io.PrintStream;
+import java.util.Iterator;
+
+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;
+
+/**
+ *
+ */
+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/test/java/org/aspectj/testing/util/SFileReader.java b/testing/src/test/java/org/aspectj/testing/util/SFileReader.java
new file mode 100644
index 000000000..eecf2d304
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/util/SFileReader.java
@@ -0,0 +1,184 @@
+/* *******************************************************************
+ * 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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+
+import org.aspectj.bridge.AbortException;
+import org.aspectj.bridge.IMessage;
+import org.aspectj.util.LangUtil;
+
+
+/**
+ * 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");
+ }
+ UtilLineReader reader = null;
+ try {
+ reader = UtilLineReader.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(UtilLineReader 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(UtilLineReader reader) {
+ return reader + ": " + reader.lastLine();
+ }
+ public Class getType() { return String.class; }
+ };
+ }
+}
diff --git a/testing/src/test/java/org/aspectj/testing/util/StandardObjectChecker.java b/testing/src/test/java/org/aspectj/testing/util/StandardObjectChecker.java
new file mode 100644
index 000000000..bc2f520ad
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/util/StreamSniffer.java b/testing/src/test/java/org/aspectj/testing/util/StreamSniffer.java
new file mode 100644
index 000000000..cb143604e
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/util/StreamsHandler.java b/testing/src/test/java/org/aspectj/testing/util/StreamsHandler.java
new file mode 100644
index 000000000..fc69d9900
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/util/StringAccumulator.java b/testing/src/test/java/org/aspectj/testing/util/StringAccumulator.java
new file mode 100644
index 000000000..234592386
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/util/StringVisitor.java b/testing/src/test/java/org/aspectj/testing/util/StringVisitor.java
new file mode 100644
index 000000000..123376df4
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/util/StructureModelUtil.java b/testing/src/test/java/org/aspectj/testing/util/StructureModelUtil.java
new file mode 100644
index 000000000..10e03dd22
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/util/StructureModelUtil.java
@@ -0,0 +1,263 @@
+/* *******************************************************************
+ * 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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import org.aspectj.asm.AsmManager;
+import org.aspectj.asm.IHierarchy;
+import org.aspectj.asm.IProgramElement;
+
+/**
+ * Prototype functionality for package view clients.
+ */
+public class StructureModelUtil {
+
+ public static class ModelIncorrectException extends Exception {
+
+ private static final long serialVersionUID = 8920868549577870993L;
+
+ public ModelIncorrectException(String s) {
+ super(s);
+ }
+ }
+
+ /**
+ * Check the properties of the current model. The parameter string lists properties of the model that should be correct. If any
+ * of the properties are incorrect, a ModelIncorrectException is thrown.
+ *
+ * @param toCheck comma separated list of name=value pairs that should be found in the ModelInfo object
+ * @throws ModelIncorrectException thrown if any of the name=value pairs in toCheck are not found
+ */
+ public static void checkModel(String toCheck) throws ModelIncorrectException {
+ Properties modelProperties = AsmManager.lastActiveStructureModel.summarizeModel().getProperties();
+
+ // Break toCheck into pieces and check each exists
+ StringTokenizer st = new StringTokenizer(toCheck, ",=");
+ while (st.hasMoreTokens()) {
+ String key = st.nextToken();
+ String expValue = st.nextToken();
+ boolean expectingZero = false;
+ try {
+ expectingZero = (Integer.parseInt(expValue) == 0);
+ } catch (NumberFormatException nfe) {
+ // this is ok as expectingZero will be false
+ }
+ String value = modelProperties.getProperty(key);
+ if (value == null) {
+ if (!expectingZero)
+ throw new ModelIncorrectException("Couldn't find '" + key + "' property for the model");
+ } else if (!value.equals(expValue)) {
+ throw new ModelIncorrectException("Model property '" + key + "' incorrect: Expected " + expValue + " but found "
+ + value);
+ }
+ }
+ }
+
+ /**
+ * This method returns a map from affected source lines in a class to a List of aspects affecting that line. Based on method of
+ * same name by mik kirsten. To be replaced when StructureModelUtil corrects its implementation
+ *
+ * @param the full path of the source file to get a map for
+ *
+ * @return a Map from line numbers to a List of ProgramElementNodes.
+ */
+ public static Map getLinesToAspectMap(String sourceFilePath) {
+
+ // Map annotationsMap =
+ // AsmManager.getDefault().getInlineAnnotations(
+ // sourceFilePath,
+ // true,
+ // true);
+
+ Map aspectMap = new HashMap();
+ // Set keys = annotationsMap.keySet();
+ // for (Iterator it = keys.iterator(); it.hasNext();) {
+ // Object key = it.next();
+ // List annotations = (List) annotationsMap.get(key);
+ // for (Iterator it2 = annotations.iterator(); it2.hasNext();) {
+ // IProgramElement node = (IProgramElement) it2.next();
+
+ // List relations = node.getRelations();
+ //
+ // for (Iterator it3 = relations.iterator(); it3.hasNext();) {
+ // IRelationship relationNode = (IRelationship) it3.next();
+
+ // if (relationNode.getKind().equals("Advice")) {
+ // List children = relationNode.getTargets();
+ //
+ // List aspects = new Vector();
+ //
+ // for (Iterator it4 = children.iterator();
+ // it4.hasNext();
+ // ) {
+ // Object object = it4.next();
+ //
+ // // if (object instanceof LinkNode) {
+ // // IProgramElement pNode =
+ // // ((LinkNode) object).getProgramElementNode();
+ // //
+ // // if (pNode.getProgramElementKind()
+ // // == IProgramElement.Kind.ADVICE) {
+ // //
+ // // IProgramElement theAspect = pNode.getParent();
+ // //
+ // // aspects.add(theAspect);
+ // //
+ // // }
+ // // }
+ // }
+ // if (!aspects.isEmpty()) {
+ // aspectMap.put(key, aspects);
+ // }
+ // }
+ //
+ // }
+ // }
+ // }
+ return aspectMap;
+ }
+
+ /**
+ * This method is copied from StructureModelUtil inoder for it to use the working version of getLineToAspectMap()
+ *
+ * @return the set of aspects with advice that affects the specified package
+ */
+ public static Set getAspectsAffectingPackage(IProgramElement packageNode) {
+ List files = StructureModelUtil.getFilesInPackage(packageNode);
+ Set aspects = new HashSet();
+ for (Iterator it = files.iterator(); it.hasNext();) {
+ IProgramElement fileNode = (IProgramElement) it.next();
+ Map adviceMap = getLinesToAspectMap(fileNode.getSourceLocation().getSourceFile().getAbsolutePath());
+ Collection values = adviceMap.values();
+ for (Iterator it2 = values.iterator(); it2.hasNext();) {
+ aspects.add(it2.next());
+ }
+ }
+ return aspects;
+ }
+
+ public static List getPackagesInModel(AsmManager modl) {
+ List packages = new ArrayList();
+ IHierarchy model = modl.getHierarchy();
+ if (model.getRoot().equals(IHierarchy.NO_STRUCTURE)) {
+ return null;
+ } else {
+ return getPackagesHelper(model.getRoot(), IProgramElement.Kind.PACKAGE, null, packages);
+ }
+ }
+
+ private static List getPackagesHelper(IProgramElement node, IProgramElement.Kind kind, String prename, List matches) {
+
+ if (kind == null || node.getKind().equals(kind)) {
+ if (prename == null) {
+ prename = new String(node.toString());
+ } else {
+ prename = new String(prename + "." + node);
+ }
+ Object[] o = new Object[2];
+ o[0] = node;
+ o[1] = prename;
+
+ matches.add(o);
+ }
+
+ for (Iterator it = node.getChildren().iterator(); it.hasNext();) {
+ IProgramElement nextNode = (IProgramElement) it.next();
+ getPackagesHelper(nextNode, kind, prename, matches);
+ }
+
+ return matches;
+ }
+
+ /**
+ * Helper function sorts a list of resources into alphabetical order
+ */
+ // private List sortElements(List oldElements) {
+ // Object[] temp = oldElements.toArray();
+ // SortingComparator comparator = new SortingComparator();
+ //
+ // Arrays.sort(temp, comparator);
+ //
+ // List newResources = Arrays.asList(temp);
+ //
+ // return newResources;
+ // }
+ //
+ // private static List sortArray(List oldElements) {
+ // Object[] temp = oldElements.toArray();
+ // SortArrayComparator comparator = new SortArrayComparator();
+ //
+ // Arrays.sort(temp, comparator);
+ //
+ // List newElements = Arrays.asList(temp);
+ //
+ // return newElements;
+ // }
+ // private class SortingComparator implements Comparator {
+ // public int compare(Object o1, Object o2) {
+ // IProgramElement p1 = (IProgramElement) o1;
+ // IProgramElement p2 = (IProgramElement) o2;
+ //
+ // String name1 = p1.getName();
+ // String name2 = p2.getName();
+ //
+ // return name1.compareTo(name2);
+ // }
+ // }
+ //
+ // private static class SortArrayComparator implements Comparator {
+ // public int compare(Object o1, Object o2) {
+ // Object[] array1 = (Object[]) o1;
+ // Object[] array2 = (Object[]) o2;
+ //
+ // IProgramElement p1 = (IProgramElement) array1[1];
+ // IProgramElement p2 = (IProgramElement) array2[1];
+ //
+ // String name1 = p1.getName();
+ // String name2 = p2.getName();
+ //
+ // return name1.compareTo(name2);
+ // }
+ // }
+ /**
+ * @return all of the AspectJ and Java source files in a package
+ */
+ public static List getFilesInPackage(IProgramElement packageNode) {
+ List packageContents;
+ if (packageNode == null) {
+ return null;
+ } else {
+ packageContents = packageNode.getChildren();
+ }
+ List files = new ArrayList();
+ for (Iterator it = packageContents.iterator(); it.hasNext();) {
+ IProgramElement packageItem = (IProgramElement) it.next();
+ if (packageItem.getKind() == IProgramElement.Kind.FILE_JAVA
+ || packageItem.getKind() == IProgramElement.Kind.FILE_ASPECTJ) {
+ files.add(packageItem);
+ }
+ }
+ return files;
+ }
+}
diff --git a/testing/src/test/java/org/aspectj/testing/util/TestClassLoader.java b/testing/src/test/java/org/aspectj/testing/util/TestClassLoader.java
new file mode 100644
index 000000000..dd9c10c8f
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/util/TestClassLoader.java
@@ -0,0 +1,157 @@
+/* *******************************************************************
+ * 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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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 {
+
+ /** seek classes in dirs first */
+ List /*File*/ dirs;
+
+ /** save URL[] only for toString */
+ private URL[] urlsForDebugString;
+
+ public TestClassLoader(URL[] urls, File[] dirs) {
+ super(urls);
+ this.urlsForDebugString = urls;
+ LangUtil.throwIaxIfComponentsBad(dirs, "dirs", null);
+ ArrayList dcopy = new ArrayList();
+
+ if (!LangUtil.isEmpty(dirs)) {
+ 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;
+ }
+
+ /** @return null if class not found or byte[] of class otherwise */
+ private byte[] readClass(String className) throws ClassNotFoundException {
+ 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);
+ }
+ }
+ return null;
+ }
+
+ 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;
+ }
+
+ /** @return String with debug info: urls and classes used */
+ public String toString() {
+ return "TestClassLoader(urls="
+ + Arrays.asList(urlsForDebugString)
+ + ", dirs="
+ + dirs
+ + ")";
+ }
+}
+
diff --git a/testing/src/test/java/org/aspectj/testing/util/TestDiffs.java b/testing/src/test/java/org/aspectj/testing/util/TestDiffs.java
new file mode 100644
index 000000000..54f72e9b9
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.util;
+
+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;
+
+import org.aspectj.util.LangUtil;
+
+/**
+ * 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 = Diffs.makeDiffs("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 = Diffs.makeDiffs("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/test/java/org/aspectj/testing/util/UtilLineReader.java b/testing/src/test/java/org/aspectj/testing/util/UtilLineReader.java
new file mode 100644
index 000000000..ebb6c9d98
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/util/UtilLineReader.java
@@ -0,0 +1,212 @@
+/* *******************************************************************
+ * 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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+
+package org.aspectj.testing.util;
+
+import java.io.*;
+import java.util.ArrayList;
+
+/**
+ * LineNumberReader which absorbs comments and blank lines
+ * and renders as file:line
+ */
+public class UtilLineReader 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 UtilLineReader createTester(File file) {
+ return create(file, TESTER_LEAD, null);
+ }
+
+ /**
+ * convenience factory
+ * @return null if IOException or IllegalArgumentException thrown
+ */
+ public static final UtilLineReader create(File file,
+ String[] leadComments, String[] eolComments) {
+ try {
+ FileReader reader = new FileReader(file);
+ return new UtilLineReader(reader, file, leadComments, eolComments);
+ } catch (IllegalArgumentException e) {
+ } catch (IOException e) {
+ }
+ return null;
+ }
+
+ final private File file;
+ final private String[] eolComments;
+ final private String[] leadComments;
+ transient String lastLine;
+
+ /**
+ * @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 UtilLineReader(FileReader reader, File file,
+ String[] leadComments, String[] eolComments) {
+ super(reader);
+ this.file = file;
+ this.eolComments = normalize(eolComments);
+ this.leadComments = normalize(leadComments);
+ }
+ public UtilLineReader(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();
+ }
+
+ /**
+ * lastLine is set only by readClippedLine, not readLine.
+ * @return the last line read, after clipping
+ */
+ public String lastLine() {
+ return lastLine;
+ }
+
+ /**
+ * 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 skipEmpties if true, run to next non-empty line; if false, return next line
+ * @return null if no more lines or got an empty line when they are not ok,
+ * or next non-null, non-empty line in reader,
+ * ignoring comments
+ */
+ public String nextLine(boolean skipEmpties) throws IOException {
+ String result;
+ do {
+ result = readClippedLine();
+ if ((null != result) && skipEmpties && (0 == result.length())) {
+ continue;
+ }
+ return result;
+ } while (true);
+ }
+
+ /** @return null if no more lines or a clipped line otherwise */
+ protected String readClippedLine() throws IOException {
+ String result = readLine();
+ if (result != null) {
+ result = result.trim();
+ int len = result.length();
+ for (int i = 0; ((0 < len) && (i < leadComments.length)); i++) {
+ if (result.startsWith(leadComments[i])) {
+ result = "";
+ len = 0;
+ }
+ }
+ for (int i = 0; ((0 < len) && (i < eolComments.length)); i++) {
+ int loc = result.indexOf(eolComments[i]);
+ if (-1 != loc) {
+ result = result.substring(0, loc);
+ len = result.length();
+ }
+ }
+ }
+ lastLine = result;
+ 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/test/java/org/aspectj/testing/util/UtilTests.java b/testing/src/test/java/org/aspectj/testing/util/UtilTests.java
deleted file mode 100644
index f5583725d..000000000
--- a/testing/src/test/java/org/aspectj/testing/util/UtilTests.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/* *******************************************************************
- * 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 Eclipse Public License v1.0
- * which accompanies this distribution and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- * Xerox/PARC initial implementation
- * ******************************************************************/
-
-
-package org.aspectj.testing.util;
-
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-
-public class UtilTests extends TestCase {
-
- public static Test suite() {
- TestSuite suite = new TestSuite(UtilTests.class.getName());
- //$JUnit-BEGIN$
- suite.addTestSuite(BridgeUtilTest.class);
- suite.addTestSuite(FileUtilTest.class);
- suite.addTestSuite(IteratorWrapperTest.class);
- suite.addTestSuite(LangUtilTest.class);
- suite.addTestSuite(MessageUtilTest.class);
- suite.addTestSuite(StreamGrabberTest.class);
- suite.addTestSuite(StructureModelUtilTest.class);
- //$JUnit-END$
- return suite;
- }
-
- public UtilTests(String name) { super(name); }
-
-}
diff --git a/testing/src/test/java/org/aspectj/testing/util/ValidFileFilter.java b/testing/src/test/java/org/aspectj/testing/util/ValidFileFilter.java
new file mode 100644
index 000000000..8d069af0f
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/util/WebInstall.java b/testing/src/test/java/org/aspectj/testing/util/WebInstall.java
new file mode 100644
index 000000000..3433d55b6
--- /dev/null
+++ b/testing/src/test/java/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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-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/test/java/org/aspectj/testing/util/options/Option.java b/testing/src/test/java/org/aspectj/testing/util/options/Option.java
new file mode 100644
index 000000000..e845a89bf
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/util/options/Option.java
@@ -0,0 +1,707 @@
+/* *******************************************************************
+ * Copyright (c) 2003 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Wes Isberg initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.util.options;
+
+import java.util.*;
+
+import org.aspectj.util.LangUtil;
+
+/**
+ * Immutable schema for an input (command-line) option.
+ * The schema contains the expected name/label,
+ * the family (for comparison purposes),
+ * and permitted prefixes.
+ * This has operations to accept input values and compare options.
+ * Options cannot be created directly; for that, use an
+ * <code>Option.Factory</code>, since it enforces uniqueness
+ * within the families and options created by the factory.
+ * <p>
+ * Option is used with related nested classes to implement relations:
+ * <ul>
+ * <li>Option.Factory produces Option</li>
+ * <li>An Option has a set of Option.Prefixes,
+ * which are variants of Option.Prefix
+ * valid for the option (e.g., on/set, force-off, and force-on)</li>
+ * <li>Option evaluates input, produces Option.Value</li>
+ * <li>Related instances of Option share an Option.Family,
+ * which enforce option exclusion, etc.</li>
+ * </ul>
+ * The classes are nested as "friends" in order to hide private
+ * members (esp. constructors) that can be used within relations.
+ * Most of these classes are immutable.
+ */
+public class Option implements Comparable {
+ public static final Prefix ON = new Prefix("-", "on", true, false);
+ public static final Prefix NONE = new Prefix("", "none", true, false);
+ public static final Prefix FORCE_ON =
+ new Prefix("!", "force-on", true, true);
+
+ public static final Prefix FORCE_OFF =
+ new Prefix("^", "force-off", false, true);
+ public static final Prefixes LITERAL_PREFIXES =
+ new Prefixes(new Prefix[] { NONE });
+ public static final Prefixes STANDARD_PREFIXES =
+ new Prefixes(new Prefix[] { ON });
+ public static final Prefixes FORCE_PREFIXES =
+ new Prefixes(new Prefix[] { ON, FORCE_ON, FORCE_OFF });
+
+ /** family is the key for comparing two options */
+ private final Family family;
+
+ /** label expected for the option */
+ private final String name;
+
+ /** unique identifier for option */
+ private final String optionIdentifier;
+
+ /** prefixes permitted for the option in input */
+ private final Prefixes permittedPrefixes;
+
+ /** if true, then match on input that has extra suffix beyond prefix and name */
+ private final boolean acceptSuffixedInput;
+
+ /**
+ * If true, no collision if there are multiple values
+ * that share the same family but not the same literal value
+ */
+ private final boolean permitMultipleValues;
+
+ /** int number of arguments expected after the option itself */
+ private final int numArguments;
+
+ /**
+ * If numArguments > 0, each element has a list of arguments
+ * permitted at that index from the initial matching value.
+ * Elements are not null.
+ */
+ private final String[][] permittedArguments;
+
+ private final int nameLength;
+
+ /*
+ * Create a standard named boolean option,
+ * permitting force-on and force-off.
+ * @param name the String name of the option, e.g., "1.3" for "-1.3"
+ * @param family
+ * @param permittedPrefixes
+ * @param acceptSuffixedInput
+ * @param permittedArguments
+ */
+ public Option(
+ String name,
+ Family family,
+ Prefixes permittedPrefixes,
+ boolean acceptSuffixedInput,
+ String[][] permittedArguments) {
+ LangUtil.throwIaxIfNull(name, "name");
+ LangUtil.throwIaxIfNull(family, "family");
+ LangUtil.throwIaxIfNull(permittedPrefixes, "permittedPrefixes");
+ this.name = name;
+ this.nameLength = name.length();
+ this.family = family;
+ this.permittedPrefixes = permittedPrefixes;
+ this.acceptSuffixedInput = acceptSuffixedInput;
+ this.permitMultipleValues = false;
+ if (LangUtil.isEmpty(permittedArguments)) {
+ permittedArguments = new String[][] { };
+ // nominal, unused
+ } else {
+ String[][] temp = new String[permittedArguments.length][];
+ for (int i = 0; i < temp.length; i++) {
+ String[] toCopy = permittedArguments[i];
+ LangUtil.throwIaxIfNull(toCopy, "no permitted args");
+ final int len = toCopy.length;
+ String[] variants = new String[len];
+ System.arraycopy(toCopy, 0, variants, 0, len);
+ temp[i] = variants;
+ }
+ permittedArguments = temp;
+ }
+ this.permittedArguments = permittedArguments;
+ numArguments = permittedArguments.length;
+ optionIdentifier = family.familyName + "." + name;
+ }
+
+ public int compareTo(Object other) {
+ Option option = (Option) other;
+ int diff = family.compareTo(option.family);
+ if (0 == diff) {
+ diff = name.compareTo(option.name);
+ }
+ return diff;
+ }
+
+ public Family getFamily() {
+ return family;
+ }
+
+ public boolean permitMultipleValues() {
+ return permitMultipleValues;
+ }
+
+ /**
+ * @return int number of elements in this option,
+ * e.g., 0 for -g or 1 for -source 1.4
+ */
+ public int numArguments() {
+ return numArguments;
+ }
+
+ /**
+ * If this value String represents a valid input for this option,
+ * then create and return the associated Value.
+ *
+ * @param value the Value created, or null if invalid
+ * @return Value if this value is permitted by this option
+ */
+ public Value acceptValue(String value) {
+ Prefix prefix = hasPrefix(value);
+ if (null != prefix) {
+ if (value.startsWith(name, prefix.length())) {
+ value = value.substring(prefix.length());
+ if (value.length() == nameLength) {
+ return new Value(value, prefix, this);
+ } else if (acceptSuffixedInput) {
+ return new Value(value, prefix, this);
+ } else {
+ return rejectingSuffixedInput(value);
+ }
+ }
+ }
+ return null;
+ }
+
+ /** @return true if we have same option family */
+ public boolean sameOptionFamily(Option other) {
+ return ((null != other) && other.family.equals(family));
+ }
+
+ /** @return true if we have same option family and name */
+ public boolean sameOptionIdentifier(Option other) {
+ return (sameOptionFamily(other) && name.equals(other.name));
+ }
+
+ public String toString() {
+ return name;
+ }
+
+ /**
+ * Called when ignoreSuffix is off but we got value with suffix.
+ */
+ protected Value rejectingSuffixedInput(String value) {
+ return null;
+ }
+
+ /**
+ * Verify that the input is permitted at this position.
+ * @param input the String input to check for validity
+ * @param position the int proposed position (0-based)
+ * for the input (position 0 is for first argument)
+ * @return null if this input is valid at this position,
+ * or a String error message otherwise.
+ */
+ String validArgument(String input, int position) {
+ if (null == input) {
+ return "null input";
+ }
+ // assert numArguments == permittedInput.length
+ if ((position < 0) || (position >= numArguments)) {
+ return "no input permitted at " + position;
+ }
+ String[] permitted = permittedArguments[position];
+ for (int i = 0; i < permitted.length; i++) {
+ if (input.equals(permitted[i])) {
+ return null;
+ }
+ }
+ return input + " not permitted, expecting one of "
+ + Arrays.asList(permitted);
+ }
+
+ String getName() {
+ return name;
+ }
+ Object getKey() {
+ return family;
+ }
+
+ private String optionIdentifier() {
+ return optionIdentifier;
+ }
+
+ private Prefix hasPrefix(String value) {
+ for (Iterator iter = permittedPrefixes.iterator();
+ iter.hasNext();
+ ) {
+ Prefix prefix = (Prefix) iter.next();
+ if (-1 != prefix.prefixLength(value)) {
+ return prefix;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * An option family identifies a set of related options that
+ * might share no literal specification.
+ * E.g., the compiler family of options might include
+ * -ajc and -eclipse, and the debugInfo family of options
+ * might include -g and -g:vars.
+ * Option families may permit or avoid option collisions.
+ * <p>
+ * For subclasses to permit some collisions and not others,
+ * they should set permitMultipleFamilyValues to false
+ * and implement <code>doCollision(Option, Option)</code>.
+ * <p>
+ * This relies on Factory to ensure that familyName is
+ * a unique identifier for the factory.
+ */
+ public static class Family implements Comparable {
+
+ /** unique String identifier for this family */
+ private final String familyName;
+
+ /** if true, then report no collisions */
+ private final boolean permitMultipleFamilyValues;
+
+ protected Family(
+ String familyName,
+ boolean permitMultipleFamilyValues) {
+ this.familyName = familyName;
+ this.permitMultipleFamilyValues = permitMultipleFamilyValues;
+ }
+
+ public int compareTo(Object arg0) {
+ Family family = (Family) arg0;
+ return familyName.compareTo(family.familyName);
+ }
+
+ public boolean sameFamily(Family family) {
+ return (
+ (null != family) && familyName.equals(family.familyName));
+ }
+
+ boolean permitMultipleFamilyValues() {
+ return permitMultipleFamilyValues;
+ }
+
+ /**
+ * Options collide if they share the same family
+ * but are not the same,
+ * and multiple values are not permitted by the family.
+ * @param lhs the Option to compare with rhs
+ * @param rhs the Option to compare with lhs
+ * @return true if the two options collide, false otherwise
+ * @throws IllegalArgumentException if the input differ
+ * and share the same family, but this isn't it.
+ */
+ public final boolean collision(Option lhs, Option rhs) {
+ if ((lhs == rhs) || (null == lhs) || (null == rhs)) {
+ return false;
+ }
+ Family lhsFamily = lhs.getFamily();
+ Family rhsFamily = rhs.getFamily();
+ if (!(lhsFamily.sameFamily(rhsFamily))) {
+ return false;
+ }
+ if (lhs.sameOptionIdentifier(rhs)) {
+ return false;
+ }
+ if (this != lhsFamily) {
+ String s =
+ "expected family " + this +" got family " + lhsFamily;
+ throw new IllegalArgumentException(s);
+ }
+ return doCollision(lhs, rhs);
+ }
+
+ /**
+ * Subclasses implement this to resolve collisions on
+ * a case-by-case basis. Input are guaranteed to be
+ * non-null, different, and to share this family.
+ * This implementation returns
+ * <code>!permitMultipleFamilyValues</code>.
+ *
+ * @param lhs the Option to compare
+ * @param rhs the other Option to compare
+ * @return true if there is a collision.
+ */
+ protected boolean doCollision(Option lhs, Option rhs) {
+ return !permitMultipleFamilyValues;
+ }
+ }
+
+ /**
+ * A factory enforces a namespace on options.
+ * All options produced from a given factory are unique,
+ * as are all families.
+ * Once an option or family is created, it cannot be changed.
+ * To have a family permit multiple values
+ * (i.e., ignore collisions), set up the family before any
+ * associated options are created using
+ * <code>setupFamily(String, boolean)</code>.
+ */
+ public static class Factory {
+ private final String factoryName;
+
+ /** enforce uniqueness of family */
+ private final Map familyNameToFamily = new TreeMap();
+
+ /** enforce uniqueness of options */
+ private final ArrayList names = new ArrayList();
+
+ public Factory(String factoryName) {
+ this.factoryName = factoryName;
+ }
+
+ /**
+ * Ensure that the family with this name has the
+ * specified permission. If the family does not exist,
+ * it is created. If it does, the permission is checked.
+ * If this returns false, there is no way to change the
+ * family permission.
+ * @param name the String identifier for the family
+ * @param permitMultipleValues the boolean permission whether to
+ * allow multiple values in this family
+ * @return true if family exists with this name and permission
+ */
+ public boolean setupFamily(
+ String name,
+ boolean permitMultipleValues) {
+ LangUtil.throwIaxIfNull(name, "name");
+ Family family;
+ synchronized (familyNameToFamily) {
+ family = (Family) familyNameToFamily.get(name);
+ if (null == family) {
+ family = new Family(name, permitMultipleValues);
+ familyNameToFamily.put(name, family);
+ } else if (
+ permitMultipleValues
+ != family.permitMultipleFamilyValues) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Register a family with this factory.
+ * @return null if the family was successfully registered,
+ * or a String error otherwise
+ */
+ public String registerFamily(Family family) {
+ if (null == family) {
+ return "null family";
+ }
+ synchronized (familyNameToFamily) {
+ Family knownFamily =
+ (Family) familyNameToFamily.get(family.familyName);
+ if (null == knownFamily) {
+ familyNameToFamily.put(family.familyName, family);
+ } else if (!knownFamily.equals(family)) {
+ return "different family registered, have "
+ + knownFamily
+ + " registering "
+ + family;
+ }
+ }
+
+ return null;
+ }
+
+ public Option create(String name) {
+ return create(name, name, FORCE_PREFIXES, false);
+ }
+
+ public Option create(
+ String name,
+ String family,
+ Prefixes permittedPrefixes,
+ boolean acceptSuffixedInput) {
+ return create(
+ name,
+ family,
+ permittedPrefixes,
+ acceptSuffixedInput,
+ (String[][]) null);
+ }
+
+ public Option create(
+ String name,
+ String family,
+ Prefixes permittedPrefixes,
+ boolean acceptSuffixedInput,
+ String[][] permittedArguments) {
+ LangUtil.throwIaxIfNull(name, "name");
+ LangUtil.throwIaxIfNull(family, "family");
+ LangUtil.throwIaxIfNull(
+ permittedPrefixes,
+ "permittedPrefixes");
+ Family resolvedFamily;
+ synchronized (familyNameToFamily) {
+ resolvedFamily = (Family) familyNameToFamily.get(family);
+ if (null == resolvedFamily) {
+ resolvedFamily = new Family(family, false);
+ familyNameToFamily.put(family, resolvedFamily);
+ }
+ }
+ Option result =
+ new Option(
+ name,
+ resolvedFamily,
+ permittedPrefixes,
+ acceptSuffixedInput,
+ permittedArguments);
+ synchronized (names) {
+ String optionIdentifier = result.optionIdentifier();
+ if (names.contains(optionIdentifier)) {
+ String s = "not unique: " + result;
+ throw new IllegalArgumentException(s);
+ } else {
+ names.add(optionIdentifier);
+ }
+ }
+ return result;
+ }
+
+ public String toString() {
+ return Factory.class.getName() + ": " + factoryName;
+ }
+
+// private void checkUnique(Option result) {
+// String name = result.family + "." + result.name;
+// }
+ }
+
+ /**
+ * The actual input value for an option.
+ * When an option takes arguments, all the arguments
+ * are absorbed/flattened into its value.
+ */
+ public static class Value {
+ private static final String FLATTEN_DELIM = "_";
+
+ private static final int NOTARGUMENT = -1;
+
+ private static String flatten(String prefix, String suffix) {
+ return prefix + FLATTEN_DELIM + suffix;
+ }
+
+ private static String[] unflatten(Value value) {
+ if (value.argIndex == Value.NOTARGUMENT) {
+ return new String[] { value.value };
+ }
+ StringTokenizer st =
+ new StringTokenizer(value.value, FLATTEN_DELIM);
+ String[] result = new String[st.countTokens()];
+ // assert result.length == 1+inputIndex
+ for (int i = 0; i < result.length; i++) {
+ result[i] = st.nextToken();
+ }
+ return result;
+
+ }
+
+ public final String value;
+ public final Prefix prefix;
+ public final Option option;
+ private final int argIndex;
+
+ private Value(
+ String value,
+ Prefix prefix,
+ Option option) {
+ this(value, prefix, option, NOTARGUMENT);
+ }
+ private Value(
+ String value,
+ Prefix prefix,
+ Option option,
+ int argIndex) {
+ this.value = value;
+ this.prefix = prefix;
+ this.option = option;
+ this.argIndex = argIndex;
+ // asserts deferred - local clients only
+ // assert null != value
+ // assert null != prefix
+ // assert null != option
+ // assert 0 <= inputIndex
+ // assert inputIndex <= option.numArguments()
+ // assert {number of DELIM} == argIndex
+ }
+
+ public String[] unflatten() {
+ return unflatten(this);
+ }
+
+ /**
+ * Create new value same as this, but with new prefix.
+ * If the prefix is the same, return this.
+ * @param prefix the Prefix to convert to
+ * @return Value with new prefix - never null
+ */
+ public Value convert(Prefix prefix) {
+ LangUtil.throwIaxIfNull(prefix, "prefix");
+ if (this.prefix.equals(prefix)) {
+ return this;
+ }
+ return new Value(
+ this.value,
+ prefix,
+ this.option,
+ this.argIndex);
+ }
+
+ /**
+ *
+ * @param other
+ * @return true if other == this for purposes of collisions
+ */
+ public boolean sameValueIdentifier(Value other) {
+ return (
+ (null != other)
+ && sameValueIdentifier(option, other.value));
+ }
+
+ public boolean sameValueIdentifier(Option option, String value) {
+ return (
+ (null != option)
+ && this.option.sameOptionIdentifier(option)
+ && this.value.equals(value));
+ }
+
+ public boolean conflictsWith(Value other) {
+ return (
+ (null != other)
+ && option.equals(other.option)
+ && (prefix.force == other.prefix.force)
+ && ((prefix.set != other.prefix.set)
+ || !value.equals(other.value)));
+ }
+
+ public String toString() {
+ return option + "=" + prefix + value;
+ }
+
+ final Value nextInput(String input) throws InvalidInputException {
+ final int index = argIndex + 1;
+ String err = option.validArgument(input, index);
+ if (null != err) {
+ throw new InvalidInputException(err, input, option);
+ }
+ return new Value(flatten(value, input), prefix, option, index);
+ }
+ }
+
+ /**
+ * A bunch of prefixes.
+ */
+ public static class Prefixes {
+ final List list;
+ private Prefixes(Prefix[] prefixes) {
+ if (LangUtil.isEmpty(prefixes)) {
+ list = Collections.EMPTY_LIST;
+ } else {
+ list =
+ Collections.unmodifiableList(
+ Arrays.asList(
+ LangUtil.safeCopy(prefixes, new Prefix[0])));
+ }
+ }
+ public Iterator iterator() {
+ return list.iterator();
+ }
+ }
+
+ /**
+ * A permitted prefix for an option, mainly so that options
+ * "-verbose", "^verbose", and "!verbose" can be treated
+ * as variants (set, force-off, and force-on) of the
+ * same "verbose" option.
+ */
+ public static class Prefix {
+ private final String prefix;
+ private final int prefixLength;
+ private final String name;
+ private final boolean set;
+ private final boolean force;
+ private Prefix(
+ String prefix,
+ String name,
+ boolean set,
+ boolean force) {
+ this.prefix = prefix;
+ this.name = name;
+ this.set = set;
+ this.force = force;
+ this.prefixLength = prefix.length();
+ }
+
+ /**
+ * Render a value for input if this is set.
+ * @param value the String to render as an input value
+ * @return null if value is null or option is not set,
+ * "-" + value otherwise
+ */
+ public String render(String value) {
+ return ((!set || (null == value)) ? null : "-" + value);
+ }
+
+ boolean forceOff() {
+ return force && !set;
+ }
+ boolean forceOn() {
+ return force && set;
+ }
+ public boolean isSet() {
+ return set;
+ }
+ private int length() {
+ return prefixLength;
+ }
+ private int prefixLength(String input) {
+ if ((null != input) && input.startsWith(prefix)) {
+ return length();
+ }
+ return -1;
+ }
+ public String toString() {
+ return prefix;
+ }
+ }
+
+ /**
+ * Thrown when an Option specifies required arguments,
+ * but the arguments are not available.
+ */
+ public static class InvalidInputException extends Exception {
+ public final String err;
+ public final String input;
+ public final Option option;
+ InvalidInputException(String err, String input, Option option) {
+ super(err);
+ this.err = err;
+ this.input = input;
+ this.option = option;
+ }
+ public String getFullMessage() {
+ return "illegal input \""
+ + input
+ + "\" for option "
+ + option
+ + ": "
+ + err;
+ }
+ }
+}
diff --git a/testing/src/test/java/org/aspectj/testing/util/options/Options.java b/testing/src/test/java/org/aspectj/testing/util/options/Options.java
new file mode 100644
index 000000000..04a968d88
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/util/options/Options.java
@@ -0,0 +1,152 @@
+/* *******************************************************************
+ * Copyright (c) 2003 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Wes Isberg initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.util.options;
+
+import java.util.*;
+
+import org.aspectj.util.LangUtil;
+
+/**
+ * A bunch of options that handle search boilerplate.
+ * This enforces an initialization phase by permitting
+ * options to be added only until frozen, and
+ * permitting matching only after frozen.
+ */
+public class Options {
+
+ /** if true, then perform extra checks to debug problems */
+// private static final boolean verifying = false;
+ private static final boolean FROZEN = true;
+
+ /**
+ * List input unmatched by options, if any.
+ * @param input the String[] used to generate the values
+ * @param values the Option.Value[] found from the input
+ * @return null if no values are null, String list of missed otherwise
+ */
+ public static String missedMatchError(
+ String[] input,
+ Values values) {
+ int[] missed = values.indexMissedMatches();
+ LangUtil.throwIaxIfNull(input, "input");
+ LangUtil.throwIaxIfNull(values, "values");
+ LangUtil.throwIaxIfFalse(
+ input.length == values.length(),
+ "input is not matched by values");
+ if (0 == missed.length) {
+ return null;
+ }
+ StringBuffer sb = new StringBuffer();
+ sb.append("missed values: [");
+ for (int i = 0; i < missed.length; i++) {
+ if (i > 0) {
+ sb.append(", ");
+ }
+ sb.append(missed[i] + ": " + input[missed[i]]);
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ private final ArrayList options = new ArrayList();
+ private final boolean stopAtFirstMatch;
+ private boolean frozen = !FROZEN;
+
+ public Options(boolean stopAtFirstMatch) {
+ this.stopAtFirstMatch = stopAtFirstMatch;
+ }
+
+ public void freeze() {
+ if (frozen != FROZEN) {
+ frozen = FROZEN;
+ }
+ }
+
+ public boolean isFrozen() {
+ return (frozen == FROZEN);
+ }
+
+ public void addOption(Option option) {
+ checkFrozen("adding option", !FROZEN);
+ LangUtil.throwIaxIfNull(option, "option");
+ options.add(option);
+ }
+
+ /**
+ * Associate options matched, if any, with input by index.
+ * If an input element is not matched, the corresponding
+ * result element will be null.
+ * If there are multi-argument options matched, then
+ * only the initial element will be non-null, but it
+ * will contain the accumulated value of the arguments.
+ * @param input the String[] of input
+ * @return Option.Value[] corresponding to input
+ * @throws Option.InvalidInputException when encountering
+ * invalid arguments to a matched multi-argument option.
+ */
+ public Values acceptInput(String[] input)
+ throws Option.InvalidInputException {
+ checkFrozen("matching options", FROZEN);
+ if ((null == input) || (0 == input.length)) {
+ return Values.EMPTY;
+ }
+ Option.Value[] results = new Option.Value[input.length];
+ for (int i = 0; i < input.length; i++) {
+ Option.Value result = firstMatch(input[i]);
+ final int index = i;
+ if (null != result) {
+ for (int len = result.option.numArguments();
+ len > 0;
+ len--) {
+ i++;
+ if (i >= input.length) {
+ throw new Option.InvalidInputException(
+ "not enough arguments",
+ null,
+ result.option);
+ }
+ result = result.nextInput(input[i]);
+ }
+ }
+ results[index] = result;
+ }
+ return Values.wrapValues(results);
+ }
+
+ private void checkFrozen(String actionLabel, boolean expectFrozen) {
+ if (expectFrozen != isFrozen()) {
+ if (null == actionLabel) {
+ actionLabel = "use";
+ }
+ if (expectFrozen) {
+ actionLabel = "must freeze before " + actionLabel;
+ } else {
+ actionLabel = "frozen before " + actionLabel;
+ }
+ throw new IllegalStateException(actionLabel);
+ }
+ }
+
+ private Option.Value firstMatch(String value) {
+ LangUtil.throwIaxIfNull(value, "value");
+// ArrayList list = new ArrayList();
+ for (Iterator iter = options.iterator(); iter.hasNext();) {
+ Option option = (Option) iter.next();
+ Option.Value result = option.acceptValue(value);
+ if (null != result) {
+ return result;
+ }
+ }
+ return null;
+ }
+}
diff --git a/testing/src/test/java/org/aspectj/testing/util/options/OptionsTests.java b/testing/src/test/java/org/aspectj/testing/util/options/OptionsTests.java
deleted file mode 100644
index 0b82c7dc9..000000000
--- a/testing/src/test/java/org/aspectj/testing/util/options/OptionsTests.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/* *******************************************************************
- * Copyright (c) 2003 Contributors.
- * All rights reserved.
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- * Wes Isberg initial implementation
- * ******************************************************************/
-
-
-package org.aspectj.testing.util.options;
-
-import junit.framework.*;
-
-public class OptionsTests extends TestCase {
-
- public static Test suite() {
- TestSuite suite = new TestSuite(OptionsTests.class.getName());
- //$JUnit-BEGIN$
- suite.addTestSuite(OptionsTest.class);
- //$JUnit-END$
- return suite;
- }
-
- public OptionsTests(String name) { super(name); }
-
-}
diff --git a/testing/src/test/java/org/aspectj/testing/util/options/Values.java b/testing/src/test/java/org/aspectj/testing/util/options/Values.java
new file mode 100644
index 000000000..574969080
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/util/options/Values.java
@@ -0,0 +1,714 @@
+/* *******************************************************************
+ * Copyright (c) 2003 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Wes Isberg initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.util.options;
+
+import java.util.*;
+
+import org.aspectj.testing.util.options.Option.Value;
+import org.aspectj.util.LangUtil;
+
+/**
+ * Wrapper for Value[] that handles search boilerplate.
+ */
+public class Values {
+ public static final Values EMPTY;
+ /** used by methods taking Selector to halt processing early */
+ private static final boolean VERIFYING = true;
+ private static final boolean FIND_ALL = true;
+
+ private static final String NO_ERROR = "no error";
+
+ static {
+ EMPTY = new Values(new Value[0]);
+ }
+
+ public static Values wrapValues(Value[] values) {
+ if ((null == values) || (0 == values.length)) {
+ return EMPTY;
+ }
+ return new Values(values);
+ }
+
+ public static Values wrapValues(Values[] values) {
+ if ((null == values) || (0 == values.length)) {
+ return EMPTY;
+ }
+ Value[] input = null;
+ if (values.length == 1) {
+ input = values[0].asArray();
+ LangUtil.throwIaxIfNull(input, "values");
+ } else {
+ int length = 0;
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] == null) {
+ LangUtil.throwIaxIfNull(null, "null value[" + i + "]");
+ }
+ length += values[i].length();
+ }
+ input = new Value[length];
+ length = 0;
+ Value[] temp;
+ for (int i = 0; i < values.length; i++) {
+ temp = values[i].asArray();
+ System.arraycopy(temp, 0, input, length, temp.length);
+ length += temp.length;
+ }
+ }
+ return new Values(input);
+ }
+
+ static int[] invert(int[] missed, int length) {
+ final int MAX = length;
+ final int len = MAX - missed.length;
+ final int[] result = new int[len];
+ int missedIndex = 0;
+ int resultIndex = 0;
+ for (int counter = 0; counter < MAX; counter++) {
+ // catch result up to missed
+ while (((missedIndex >= missed.length)
+ || (missed[missedIndex] > counter))
+ && (counter < MAX)) {
+ result[resultIndex++] = counter++;
+ }
+ // absorb missed up to counter
+ while ((missedIndex < missed.length)
+ && (missed[missedIndex] <= counter)
+ && (counter < MAX)) {
+ missedIndex++;
+ }
+ }
+ return result;
+ }
+
+ private static Option.Value[] toArray(ArrayList list) {
+ return (Option.Value[]) list.toArray(new Option.Value[0]);
+ }
+
+ /**
+ * Resolve input to remove any values matching the same options,
+ * where option conflicts are handled by option forcing.
+ * First, for any force-off value, all matching set-on and
+ * the force-off itself are removed. At this time, if there
+ * is a matching force-on, then this will return an error.
+ * Next, for any force-on value, it is converted to set-on,
+ * and any other matching set-on value is removed.
+ * Finally, this signals a collision if two values share
+ * the same option family and the family reports that this is
+ * a collision.
+ * In all cases, only the first error detected is reported.
+ * @param input the Option.Value[] matched from the input,
+ * (forced/duplicate options will be set to null)
+ * @return String error during resolution, or null if no error
+ */
+ private static String resolve(Option.Value[] input) {
+ String err = null;
+ if (LangUtil.isEmpty(input)) {
+ return null;
+ }
+
+ Map familyToMatches = new TreeMap();
+ for (int i = 0;(null == err) && (i < input.length); i++) {
+ if (null != input[i]) {
+ Option.Family family = input[i].option.getFamily();
+ int[] matches = (int[]) familyToMatches.get(family);
+ if (null == matches) {
+ matches = match(input, i);
+ familyToMatches.put(family, matches);
+ }
+ }
+ }
+
+ familyToMatches = Collections.unmodifiableMap(familyToMatches);
+ for (Iterator iter = familyToMatches.entrySet().iterator();
+ (null == err) && iter.hasNext();
+ ) {
+ Map.Entry entry = (Map.Entry) iter.next();
+ int[] matches = (int[]) entry.getValue();
+ err = resolve(input, matches);
+ }
+ return err;
+ }
+
+ /**
+ * Resolve all related options into one
+ * by nullifying or modifying the values.
+ *
+ * First, for any force-off value,
+ * remove all identical set-on
+ * and the force-off itself,
+ * and alert on any identical force-on.
+ *
+ * Next, for any force-on value,
+ * convert to set-on,
+ * throw Error on any same-family force-off value,
+ * remove any identical force-on or set-on value,
+ * alert on any other non-identical same-family force-on value,
+ * remove any same-family set-on value,
+ * and alert on any same-family set-off value.
+ *
+ * Finally, alert if any two remaining values share
+ * the same option family, unless the option is marked
+ * as permitting multiple values.
+ *
+ * @param input the Option.Value[] matching the input
+ * @param matches the int[] list of indexes into input for
+ * values for related by option
+ * (all such values must have option matched by family)
+ * @return String error, if any, or null if no error
+ * @see #match(Option.Value[], int)
+ */
+ private static String resolve(Option.Value[] input, int[] matches) {
+ String err = null;
+ // seek force-off
+// Option.Value forceOff = null;
+ Option option = null;
+ // find and remove any force-off
+ for (int i = 0;(null == err) && (i < matches.length); i++) {
+ Option.Value value = input[matches[i]];
+ if (null != value) {
+ // verify that matches are in the same family
+ if (VERIFYING) {
+ if (null == option) {
+ option = value.option;
+ } else if (!(option.sameOptionFamily(value.option))) {
+ String s =
+ value.option
+ + " has different family from "
+ + option;
+ throw new IllegalArgumentException(s);
+ }
+ }
+ if (value.prefix.forceOff()) {
+ err = removeForceOff(input, value, matches);
+ }
+ }
+ }
+ // find and set any force-on, removing others
+ for (int i = 0;(null == err) && (i < matches.length); i++) {
+ Option.Value value = input[matches[i]];
+ if (null != value) {
+ if (value.prefix.forceOn()) {
+ err = convertForceOn(input, i, matches);
+ }
+ }
+ }
+ // remove any exact duplicates
+ for (int i = 0;(null == err) && (i < matches.length); i++) {
+ Option.Value value = input[matches[i]];
+ if (null != value) {
+ for (int j = i + 1; j < matches.length; j++) {
+ if (value.sameValueIdentifier(input[matches[j]])) {
+ input[matches[j]] = null;
+ }
+ }
+ }
+ }
+ // signal error if two left unless permitMultipleFamilyValues
+ Option.Value first = null;
+ for (int i = 0;(null == err) && (i < matches.length); i++) {
+ Option.Value value = input[matches[i]];
+ if (null != value) {
+ if (null == first) {
+ first = value;
+ if (first
+ .option
+ .getFamily()
+ .permitMultipleFamilyValues()) {
+ break;
+ }
+ } else {
+ err = "collision between " + first + " and " + value;
+ }
+ }
+ }
+
+ return err;
+ }
+
+ /**
+ * For any force-off value,
+ * remove all set-on or force-off with same value
+ * (including the force-off itself),
+ * and alert on any identical force-on.
+ * @param input the Option.Value[] matching the input
+ * @param value the force-off Option.Value to remove
+ * @param matches the int[] list of indexes into input for
+ * values for related by option
+ * (all such values must have matching option)
+ * @return String error if any
+ */
+ private static String removeForceOff(
+ Option.Value[] input,
+ Option.Value value,
+ int[] matches) {
+ if (!value.prefix.forceOff()) {
+ throw new IllegalArgumentException(
+ "expecting force-off: " + value);
+ }
+ for (int i = 0; i < matches.length; i++) {
+ Option.Value match = input[matches[i]];
+ if ((null != match) && value.sameValueIdentifier(match)) {
+ if (match.prefix.forceOn()) {
+ return "force conflict between "
+ + value
+ + " and "
+ + match;
+ } else {
+ input[matches[i]] = null; // unset matches[i]?
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * For this force-on value, convert to set-on,
+ * throw Error on any same-family force-off value,
+ * remove any identical force-on or set-on value,
+ * alert on any other non-identical same-family force-on value,
+ * remove any same-family set-on value,
+ * and alert on any same-family set-off value.
+ * This must be called after <code>removeForceOff(..)</code>.
+ * @param input the Option.Value[] to modify
+ * @param valueIndex the int index in matches to find the force-on
+ * and to start after
+ * @param matches the int[] map into input entries with matching options
+ * @return
+ * @throw Error if any matching force-off found
+ */
+ private static String convertForceOn(
+ Option.Value[] input,
+ int valueIndex,
+ int[] matches) {
+ Option.Value value = input[matches[valueIndex]];
+ if (!value.prefix.forceOn()) {
+ throw new IllegalArgumentException(
+ "expecting force-on: " + value);
+ }
+ input[matches[valueIndex]] = value.convert(Option.ON);
+ for (int i = 0; i < matches.length; i++) {
+ if (i == valueIndex) {
+ continue;
+ }
+ Option.Value match = input[matches[i]];
+ if (null != match) {
+ // assert match.sameOptionFamily(value);
+ if (match.prefix.forceOff()) {
+ throw new Error(
+ "unexpected force-off:"
+ + match
+ + " when processing "
+ + value);
+ }
+ if (value.option.sameOptionIdentifier(match.option)) {
+ input[matches[i]] = null;
+ // remove any identical force-on or set
+ } else if (match.prefix.forceOn()) {
+ return "conflict between " + match + " and " + value;
+ } else if (match.prefix.isSet()) {
+ input[matches[i]] = null;
+ // remove any same-value set-on value
+ } else { // same family, force-off
+ return "collision between " + match + " and " + value;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get a list of input matching the option in the initial value,
+ * rendered as indexes into the input array.
+ * @param input the Option.Value[] to seek in
+ * @param start the int index of the starting position
+ * @return int[] of indexes into input with the same option
+ * as index[start] - never null, but can be empty
+ */
+ private static int[] match(Option.Value[] input, int start) {
+ IntList result = new IntList();
+ Option.Family key = null;
+ Option.Family nextKey = null;
+ for (int i = start; i < input.length; i++) {
+ if (null != input[i]) {
+ nextKey = input[i].option.getFamily();
+ if (null == key) {
+ key = nextKey;
+ result.add(i);
+ } else if (key.equals(nextKey)) {
+ result.add(i);
+ }
+ }
+ }
+ return result.getList();
+ }
+
+ static int nullify(Option.Value[] values, Selector selector) {
+ LangUtil.throwIaxIfNull(selector, "selector");
+ int changed = 0;
+ for (int i = 0; i < values.length; i++) {
+ final boolean accepted;
+ try {
+ accepted = selector.accept(values[i]);
+ } catch (Error e) {
+ if (e != Selector.STOP) {
+ throw e;
+ }
+ break;
+ }
+ if (accepted) {
+ if (null != values[i]) {
+ values[i] = null;
+ changed++;
+ }
+ }
+ }
+ return changed;
+ }
+
+ /**
+ * Render set values as String using associated prefix.
+ * @param values the Value[] to render
+ * @return String[] of values rendered for output
+ * (never null or longer than values, but might be shorter)
+ */
+ private static String[] render(Value[] values) {
+ ArrayList list = new ArrayList();
+ for (int i = 0; i < values.length; i++) {
+ if (null != values[i]) {
+ String[] output = values[i].unflatten();
+ if (LangUtil.isEmpty(output)) {
+ throw new Error("no output for " + values[i]);
+ }
+
+ String s = values[i].prefix.render(output[0]);
+ if (null != s) { // this means the prefix is set
+ list.add(s);
+ for (int j = 1; j < output.length; j++) {
+ list.add(output[j]);
+ }
+ }
+ }
+ }
+ return (String[]) list.toArray(new String[list.size()]);
+ }
+
+ private final Option.Value[] values;
+ private Option.Value[] valuesNotNull;
+ private String resolveError;
+
+ private Values(Value[] values) {
+ this.values = new Value[values.length];
+ System.arraycopy(values, 0, this.values, 0, values.length);
+ }
+
+ public int length() {
+ return values.length;
+ }
+
+ public Option.Value[] asArray() {
+ Option.Value[] result = new Option.Value[values.length];
+ System.arraycopy(values, 0, result, 0, result.length);
+ return result;
+ }
+
+ /**
+ * Emit as String[] the non-null values.
+ * @return String[] of matched entries (never null, elements not null)
+ */
+ public String[] render() {
+ return Values.render(valuesNotNull());
+ }
+
+ public String toString() {
+ return Arrays.asList(values).toString();
+ }
+
+ /**
+ * Create index into values of those that were matched,
+ * including the options and their arguments.
+ * @return int[] of elements in values that are not null (options)
+ * or that represent option arguments
+ */
+ public int[] indexMatches() {
+ // must be in order, low to high
+ final int[] missed = indexMissedMatches();
+ return invert(missed, length());
+ }
+
+ /**
+ * Create index into values of missed input,
+ * taking into account that matched arguments are
+ * represented as null.
+ * @return int[] of elements in values that are null
+ * or optionally represent option arguments
+ */
+ public int[] indexMissedMatches() {
+ MissedSelector selector = new MissedSelector();
+ find(selector, FIND_ALL);
+ String errors = selector.getErrors();
+ if (null != errors) {
+ throw new Error(errors);
+ }
+ return selector.getResult();
+ }
+
+ public Value firstInFamily(Option.Family family) {
+ return findFirst(new ValueSelector(family));
+ }
+
+ public Value[] allInFamily(Option.Family family) {
+ return find(new ValueSelector(family), FIND_ALL);
+ }
+
+ public Value firstOption(Option option) {
+ return findFirst(new ValueSelector(option));
+ }
+
+ public Value[] allOption(Option option) {
+ return find(new ValueSelector(option), FIND_ALL);
+ }
+
+ public Value firstValue(Option option, String value) {
+ LangUtil.throwIaxIfNull(value, "value");
+ return findFirst(new ValueSelector(option, value));
+ }
+
+ public Value[] allValues(Option option, String value) {
+ LangUtil.throwIaxIfNull(value, "value");
+ return find(new ValueSelector(option, value), FIND_ALL);
+ }
+
+ public boolean isResolved() {
+ return ((this != EMPTY) && (null != resolveError));
+ }
+
+ /**
+ *
+ * @param selector the Selector to pick out entries to nullify
+ * (should throw STOP to halt processing)
+ * @return Values resulting from nullifying entries,
+ * or this if none were changed
+ */
+ public Values nullify(Selector selector) {
+ if (null == selector) {
+ return this;
+ }
+ Value[] temp = asArray();
+ int changed = nullify(temp, selector);
+ if (0 == changed) {
+ return this;
+ }
+ return new Values(temp);
+ }
+
+ /**
+ * Resolve options, removing duplicates by force if necessary.
+ * If any error is returned, then the values are left unchanged.
+ * @return String error, if any
+ * @throws IllegalStateException if <code>isResolved()</code>
+ */
+ public String resolve() {
+ if (isResolved()) {
+ throw new IllegalStateException("already resolved");
+ }
+ Option.Value[] temp = asArray();
+ resolveError = resolve(temp);
+ if (null == resolveError) {
+ System.arraycopy(temp, 0, values, 0, temp.length);
+ valuesNotNull = null;
+ resolveError = NO_ERROR;
+ return null;
+ }
+ return resolveError;
+ }
+
+ protected Option.Value findFirst(Selector filter) {
+ Option.Value[] result = find(filter, !FIND_ALL);
+ return (0 == result.length ? null : result[0]);
+ }
+
+ protected Option.Value[] find(Selector filter, boolean findAll) {
+ LangUtil.throwIaxIfNull(filter, "filter");
+ ArrayList result = new ArrayList();
+ for (int i = 0; i < values.length; i++) {
+ final boolean accepted;
+ try {
+ accepted = filter.accept(values[i]);
+ } catch (Error e) {
+ if (Selector.STOP != e) {
+ throw e;
+ }
+ break;
+ }
+ if (accepted) {
+ result.add(values[i]);
+ if (findAll != FIND_ALL) {
+ break;
+ }
+ }
+ }
+ return toArray(result);
+ }
+
+ private Option.Value[] valuesNotNull() {
+ if (null == valuesNotNull) {
+ ArrayList list = new ArrayList();
+ for (int i = 0; i < this.values.length; i++) {
+ if (null != this.values[i]) {
+ list.add(this.values[i]);
+ }
+ }
+ valuesNotNull = toArray(list);
+ }
+ return valuesNotNull;
+ }
+
+ public static class Selector {
+ public static final Error STOP = new Error("stop invoking Selector");
+ protected Selector() {
+ }
+ protected boolean accept(Value value) {
+ return false;
+ }
+ }
+ protected static class ValueSelector extends Selector {
+
+ private final Option option;
+ private final Option.Family family;
+ private final String value;
+ ValueSelector(Option.Family family) {
+ LangUtil.throwIaxIfNull(family, "family");
+ this.family = family;
+ option = null;
+ value = null;
+ }
+ ValueSelector(Option option) {
+ this(option, (String) null);
+ }
+ ValueSelector(Option option, String value) {
+ LangUtil.throwIaxIfNull(option, "option");
+ this.option = option;
+ family = null;
+ this.value = value;
+ }
+ protected boolean accept(Value value) {
+ if (null == value) {
+ return false;
+ }
+ if (null != family) {
+ return family.sameFamily(value.option.getFamily());
+ } else if (!option.sameOptionIdentifier(value.option)) {
+ return false;
+ } else {
+ return ((null == this.value)
+ || (this.value.equals(value.value)));
+ }
+ }
+ }
+
+ /** pick all null entries (except for args), return as int[] */
+ protected static class MissedSelector extends Selector {
+ public static final String DELIM = "; ";
+ final IntList result = new IntList();
+ int index;
+ final StringBuffer errors = new StringBuffer();
+ int argsExpected;
+ Option argsExpectedFor;
+ MissedSelector() {
+ }
+
+ int[] getResult() {
+ return result.getList();
+ }
+
+ /**
+ * add index if value is null
+ * unless skipArguments
+ */
+ protected boolean accept(Value value) {
+ index++;
+ if (null != value) {
+ if (0 < argsExpected) { // expected more (null) args
+ missedArgsFor(argsExpectedFor, argsExpected);
+ }
+ argsExpected = value.option.numArguments();
+ argsExpectedFor = value.option;
+ } else if (0 < argsExpected) { // ignore null in arg position
+ argsExpected--;
+ if (0 == argsExpected) {
+ argsExpectedFor = null;
+ }
+ } else { // null, not expecting arg, so missing
+ result.add(index - 1);
+ return true;
+ }
+ return false;
+ }
+
+ private void missedArgsFor(Option option, int numArgs) {
+ errors.append("missed ");
+ errors.append(numArgs + " args for ");
+ errors.append(option + DELIM);
+ }
+
+ String getErrors() {
+ if (0 < argsExpected) {
+ }
+ if (0 == errors.length()) {
+ return null;
+ }
+ return errors.toString();
+ }
+ }
+
+ static class IntList {
+ // not synchronized - used only in one thread
+ static String render(int[] input) {
+ if (null == input) {
+ return "null";
+ }
+ StringBuffer sb = new StringBuffer();
+ sb.append("[");
+ for (int i = 0; i < input.length; i++) {
+ if (i > 0) {
+ sb.append(", " + input[i]);
+ } else {
+ sb.append("" + input[i]);
+ }
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ private int[] input = new int[256];
+ private int insert;
+ private void add(int i) {
+ if (insert >= input.length) {
+ int[] temp = new int[insert + 256];
+ for (int j = 0; j < input.length; j++) {
+ temp[j] = input[j];
+ }
+ input = temp;
+ }
+ input[insert++] = i;
+ }
+
+ private int[] getList() {
+ int[] result = new int[insert];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = input[i];
+ }
+ return result;
+ }
+ }
+}
diff --git a/testing/src/test/java/org/aspectj/testing/xml/AjcSpecXmlReader.java b/testing/src/test/java/org/aspectj/testing/xml/AjcSpecXmlReader.java
new file mode 100644
index 000000000..370a9ac91
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/xml/AjcSpecXmlReader.java
@@ -0,0 +1,526 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC),
+ * 2003 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * Wes Isberg resolver
+ * ******************************************************************/
+
+package org.aspectj.testing.xml;
+
+import java.io.*;
+//import java.util.Vector;
+
+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.bridge.SourceLocation;
+import org.aspectj.testing.harness.bridge.*;
+import org.aspectj.testing.util.RunUtils;
+import org.aspectj.util.LangUtil;
+import org.xml.sax.*;
+import org.xml.sax.SAXException;
+
+/**
+ * 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)
+ *
+ * Debugging
+ * - use logLevel = 2 for tracing
+ * - common mistakes
+ * - dtd has to match input
+ * - no rule defined (or misdefined) so element ignored
+ * - property read-only (?)
+ */
+
+// 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 + "\">";
+
+ 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("../tests/ajcTestSuite2.dtd"));
+ }
+
+ /**
+ * Write a DTD to dtdFile.
+ * @deprecated
+ * @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" };
+
+ // XXX logLevel n>0 causes JUnit tests to fail!
+ private int logLevel = 0; // use 2 for tracing
+
+ 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 = makeDigester(file);
+ 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 = file.getAbsoluteFile();
+ result.setSourceLocation(new SourceLocation(file, 1));
+ File suiteDir = file.getParentFile();
+ if (null == suiteDir) {
+ // should not be the case if absolute
+ suiteDir = new File("."); // user.dir?
+ }
+ result.setSuiteDirFile(suiteDir);
+ if (result.runtime.isVerbose()) { // XXX hack fixup
+ RunUtils.enableVerbose(result);
+ }
+ }
+ return result;
+ }
+
+ private Digester makeDigester(final File suiteFile) {
+ // implement EntityResolver directly; set is failing
+ Digester result = new Digester() {
+ final SuiteResolver resolver = new SuiteResolver(suiteFile);
+ public InputSource resolveEntity(
+ String publicId,
+ String systemId)
+ throws SAXException {
+ return resolver.resolveEntity(publicId, systemId);
+ }
+ };
+ setupDigester(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);
+// try {
+// // this is the correct approach, but the commons parser
+// // fails to accept a second, overriding registration - see
+// // http://lists.xml.org/archives/xml-dev/200111/msg00959.html
+// digester.getXMLReader().setEntityResolver(new SuiteResolver(suiteFile));
+// } catch (SAXException e) {
+// System.err.println("unable to set entity resolver");
+// e.printStackTrace(System.err);
+// }
+
+ // 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 + "/" +SoftSourceLocation.XMLNAME;
+
+ // ---- 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,
+ new String[] { "class", "vm", "skipTester", "fork", "vmargs", "aspectpath", "module"},
+ new String[] { "className", "javaVersion", "skipTester", "fork", "vmArgs", "aspectpath", "module"});
+ digester.addSetProperties(dirchangesX);
+ digester.addSetProperties(messageX);
+ digester.addSetProperties(messageSrcLocX, "line", "lineAsString");
+ digester.addSetProperties(messageX, "kind", "kindAsString");
+ digester.addSetProperties(messageX, "line", "lineAsString");
+ //digester.addSetProperties(messageX, "details", "details");
+ // 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());
+ // setSourceLocation is for the inline variant
+ // addSourceLocation is for the extra
+ digester.addSetNext(messageSrcLocX, "addSourceLocation", 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",
+ "staging", "badInput", "reuseCompiler", "includeClassesDir",
+ "argfiles", "aspectpath", "classpath", "extdirs",
+ "sourceroots", "xlintfile", "outjar"}),
+ new BProps(IncCompilerRun.Spec.class,
+ new String[] { "tag" }),
+ new BProps(JavaRun.Spec.class,
+ new String[] { "className", "skipTester", "options",
+ "javaVersion", "errStreamIsError", "outStreamIsError",
+ "fork", "vmArgs", "aspectpath"}),
+ // 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", "details", "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();
+ Sandbox sandbox = null;
+ Validator validator = null;
+// AjcTest ajctest = new AjcTest(test, sandbox, validator);
+// ajctest.addRunSpec((AbstractRunSpec) null);
+//// test.makeIncCompilerRun((IncCompilerRun.Spec) null);
+//// test.makeJavaRun((JavaRun.Spec) null);
+// ajctest.setDescription((String) null);
+// ajctest.setTestBaseDirOffset((String) null);
+// ajctest.setBugId((String) null);
+// ajctest.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);
+ crunSpec.setIncludeClassesDir(false);
+ crunSpec.setReuseCompiler(false);
+ crunSpec.setXlintfile((String) null);
+ crunSpec.setOutjar((String)null);
+
+ IncCompilerRun.Spec icrunSpec = new IncCompilerRun.Spec();
+ icrunSpec.addMessage((IMessage) null);
+ icrunSpec.setTag((String) null);
+ icrunSpec.setFresh(false);
+
+ 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);
+ jrunspec.setErrStreamIsError("false");
+ jrunspec.setOutStreamIsError("false");
+ jrunspec.setAspectpath("");
+ jrunspec.setClasspath("");
+ jrunspec.setFork(false);
+ jrunspec.setLTW("false");
+ jrunspec.setException("Error");
+
+
+ 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);
+ m.setDetails((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;
+ }
+ }
+
+ /**
+ * Find file NAME=="ajcTestSuite.dtd" from some reasonably-local
+ * relative directories.
+ * XXX bug: commons parser doesn't accept second registration,
+ * so we override Digester's implementation instead.
+ * XXX cannot JUnit test SuiteResolver since they run from
+ * local directory with valid reference
+ * XXX does not fix JDK 1.4 parser message "unable to resolve without base URI"
+ * XXX should be able to just set BaseURI instead...
+ */
+ static class SuiteResolver implements EntityResolver {
+ public static final String NAME = "ajcTestSuite.dtd";
+ private static boolean isDir(File dir) {
+ return ((null != dir) && dir.canRead() && dir.isDirectory());
+ }
+ private final File suiteFile;
+ public SuiteResolver(File suiteFile) {
+ this.suiteFile = suiteFile;
+ }
+
+
+ private String getPath(String id) {
+ // first, try id relative to suite file
+ final File suiteFileDir = suiteFile.getParentFile();
+ if (isDir(suiteFileDir)) {
+ String path = suiteFileDir.getPath()
+ + "/" + id;
+ File result = new File(path);
+ if (result.canRead() && result.isFile()) {
+ return result.getPath();
+ }
+ }
+ // then try misc paths relative to suite file or current dir
+ final File[] baseDirs = new File[]
+ { suiteFileDir, new File(".")
+ };
+ final String[] locations = new String[]
+ { ".", "..", "../tests", "../../tests",
+ "../../../tests", "tests", "modules/tests"
+ };
+ File baseDir;
+ for (int j = 0; j < baseDirs.length; j++) {
+ baseDir = baseDirs[j];
+ if (!isDir(baseDir)) {
+ continue;
+ }
+ for (int i = 0; i < locations.length; i++) {
+ File dir = new File(baseDir, locations[i]);
+ if (isDir(dir)) {
+ File temp = new File(dir, NAME);
+ if (temp.isFile() && temp.canRead()) {
+ return temp.getPath();
+ }
+ }
+ }
+ }
+ return null;
+ }
+ public InputSource resolveEntity(
+ String publicId,
+ String systemId)
+ throws SAXException {
+ InputSource result = null;
+ if ((null != systemId) &&
+ systemId.endsWith(NAME)) {
+ String path = getPath(systemId);
+ if (null != path) {
+ result = new InputSource(path);
+ result.setSystemId(path);
+ result.setPublicId(path);
+ }
+ }
+ return result;
+ }
+ }
+}
+
+//private String getDocTypePath() {
+// String result = null;
+// if (null != suiteFile) {
+// FileReader fr = null;
+// try {
+// fr = new FileReader(suiteFile);
+// BufferedReader reader = new BufferedReader(fr);
+// String line;
+// while (null != (line = reader.readLine())) {
+// String upper = line.trim();
+// line = upper.toLowerCase();
+// if (line.startsWith("<")) {
+// if (line.startsWith("<!doctype ")) {
+// int start = line.indexOf("\"");
+// int end = line.lastIndexOf(NAME + "\"");
+// if ((0 < start) && (start < end)) {
+// return upper.substring(start+1,
+// end + NAME.length());
+// }
+// } else if (!line.startsWith("<?xml")) {
+// break; // something else...
+// }
+// }
+// }
+// } catch (IOException e) {
+// // ignore
+// } finally {
+// if (null != fr) {
+// try {
+// fr.close();
+// } catch (IOException e1) { // ignore
+// }
+// }
+// }
+// }
+// return null;
+//}
+
diff --git a/testing/src/test/java/org/aspectj/testing/xml/TestingXmlTests.java b/testing/src/test/java/org/aspectj/testing/xml/IXmlWritable.java
index ff8093c32..a11b32be3 100644
--- a/testing/src/test/java/org/aspectj/testing/xml/TestingXmlTests.java
+++ b/testing/src/test/java/org/aspectj/testing/xml/IXmlWritable.java
@@ -11,25 +11,15 @@
* Xerox/PARC initial implementation
* ******************************************************************/
-
package org.aspectj.testing.xml;
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-
-public class TestingXmlTests extends TestCase {
-
- public static Test suite() {
- TestSuite suite = new TestSuite(TestingXmlTests.class.getName());
- //$JUnit-BEGIN$
- suite.addTestSuite(AjcSpecXmlReaderTest.class);
- suite.addTestSuite(MessageListXmlReaderTest.class);
- suite.addTestSuite(XMLWriterTest.class);
- //$JUnit-END$
- return suite;
- }
-
- public TestingXmlTests(String name) { super(name); }
-
-}
+/**
+ *
+ */
+public interface IXmlWritable {
+ /**
+ * Write self out to XML.
+ * @param out the XMLWriter to write to
+ */
+ void writeXml(XMLWriter out);
+}
diff --git a/testing/src/test/java/org/aspectj/testing/xml/MessageListXmlReader.java b/testing/src/test/java/org/aspectj/testing/xml/MessageListXmlReader.java
new file mode 100644
index 000000000..cde5e99dd
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/xml/MessageListXmlReader.java
@@ -0,0 +1,215 @@
+/* *******************************************************************
+ * Copyright (c) 2003 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Wes Isberg initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.xml;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+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.util.LangUtil;
+import org.xml.sax.SAXException;
+
+/**
+ * Read a list of messages in xml form.
+ * Input files should comply with DOCTYPE
+ */
+public class MessageListXmlReader {
+ private static final String INLINE_DOCTYPE;
+ static {
+ final String EOL = LangUtil.EOL;
+ final StringBuffer r = new StringBuffer();
+
+ r.append("<!DOCTYPE ");
+ r.append(MessageList.XMLNAME);
+ r.append(" [");
+ r.append(EOL + " <!ELEMENT " + MessageList.XMLNAME
+ + " (" + SoftMessage.XMLNAME + "+) >");
+ String name = SoftMessage.XMLNAME;
+ r.append(EOL + " <!ELEMENT " + name + " (" + SoftSourceLocation.XMLNAME + ")*>");
+ r.append(EOL + " <!ATTLIST " + name + " kind CDATA #REQUIRED >");
+ r.append(EOL + " <!ATTLIST " + name + " message CDATA #IMPLIED >");
+ name = SoftSourceLocation.XMLNAME;
+ r.append(EOL + " <!ELEMENT " + name + " (#PCDATA) >");
+ r.append(EOL + " <!ATTLIST " + name + " line CDATA #REQUIRED >");
+ r.append(EOL + " <!ATTLIST " + name + " endLine CDATA #IMPLIED >");
+ r.append(EOL + " <!ATTLIST " + name + " column CDATA #IMPLIED >");
+ r.append(EOL + " <!ATTLIST " + name + " sourceFile CDATA #IMPLIED >");
+
+ r.append(EOL + "] >");
+ INLINE_DOCTYPE = r.toString();
+ }
+
+ private static final String[] LOG = new String[] {"info", "debug", "trace" };
+
+ private int logLevel;
+
+ /**
+ * Print IMessage[] to the output file as XML.
+ * @param output the File to write to - overwritten
+ * @param messages the IMessage[] to write
+ * @return null if no warnings detected, warnings otherwise
+ */
+ public String writeMessages(File output, IMessage[] messages) throws IOException {
+ LangUtil.throwIaxIfNull(output, "output");
+ LangUtil.throwIaxIfFalse(!LangUtil.isEmpty(messages), "no messages");
+ PrintWriter writer = new PrintWriter(new FileOutputStream(output));
+ XMLWriter printSink = new XMLWriter(writer);
+ writer.println("");
+ writer.println(INLINE_DOCTYPE);
+ writer.println("");
+ writer.println("<" + MessageList.XMLNAME + ">");
+ SoftMessage.writeXml(printSink, messages);
+ writer.println("</" + MessageList.XMLNAME + ">");
+ writer.close();
+ return null;
+ }
+
+ /** @param level 0..2, info..trace */
+ public void setLogLevel(int level) {
+ if (level < 0) {
+ level = 0;
+ }
+ if (level > 2) {
+ level = 2;
+ }
+ logLevel = level;
+ }
+
+ /**
+ * Read the specifications for a list of IMessage from an XML file.
+ * @param file the File must be readable, comply with DOCTYPE.
+ * @return IMessage[] read from file
+ * @see setLogLevel(int)
+ */
+ public IMessage[] readMessages(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);
+
+ MessageListHolder holder = new MessageListHolder();
+ 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;
+ }
+ }
+ return (null == holder.list
+ ? new IMessage[0]
+ : holder.list.getMessages());
+ }
+
+ /** 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 messageListX = MessageList.XMLNAME;
+ final String messageX = messageListX + "/" + SoftMessage.XMLNAME;
+ final String messageSrcLocX = messageX + "/" + SoftSourceLocation.XMLNAME;
+
+ // ---- each sub-element needs to be created
+ // handle messages the same at any level
+ digester.addObjectCreate(messageListX, MessageList.class.getName());
+ digester.addObjectCreate(messageX, SoftMessage.class.getName());
+ digester.addObjectCreate(messageSrcLocX, SoftSourceLocation.class.getName());
+
+ // ---- set bean properties for sub-elements created automatically
+ // -- some remapped - warnings
+ // - if property exists, map will not be used
+ digester.addSetProperties(messageListX);
+ digester.addSetProperties(messageX);
+ digester.addSetProperties(messageSrcLocX);
+ digester.addSetProperties(messageX, "kind", "kindAsString");
+ digester.addSetProperties(messageX, "line", "lineAsString");
+
+ // ---- when subelements are created, add to parent
+ digester.addSetNext(messageListX, "addMessage", IMessage.class.getName());
+ digester.addSetNext(messageX, "setSourceLocation", ISourceLocation.class.getName());
+
+ // can set parent, but prefer to have "knows-about" flow down only...
+ }
+
+ // ------------------------------------------------------------ testing code
+
+ /**
+ * 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"); }
+//
+// MessageListHolder holder = new MessageListHolder();
+// MessageList ml = new MessageList();
+// SoftMessage m = new SoftMessage();
+// SoftSourceLocation sl = new SoftSourceLocation();
+//
+// holder.setMessageList(ml);
+// ml.addMessage((IMessage) null);
+// m.setSourceLocation(sl);
+// m.setText((String) null);
+// m.setKindAsString((String) null);
+//
+// sl.setFile((String) null);
+// sl.setLine((String) null);
+// sl.setColumn((String) null);
+// sl.setEndLine((String) null);
+//
+// // add attribute setters to validate?
+// }
+
+ // inner classes, to make public for bean utilities
+ /** a list of messages */
+ public static class MessageList {
+ public static final String XMLNAME = "message-list";
+ private List messages = new ArrayList();
+ public void addMessage(IMessage message) {
+ messages.add(message);
+ }
+ IMessage[] getMessages() {
+ return (IMessage[]) messages.toArray(new IMessage[0]);
+ }
+ }
+
+ /** push on digester stack to hold message list */
+ public static class MessageListHolder {
+ public MessageList list;
+ public void setMessageList(MessageList list) {
+ this.list = list;
+ }
+ }
+
+}
+
+
+
diff --git a/testing/src/test/java/org/aspectj/testing/xml/SoftMessage.java b/testing/src/test/java/org/aspectj/testing/xml/SoftMessage.java
new file mode 100644
index 000000000..f59bf397e
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/xml/SoftMessage.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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.xml;
+
+import java.io.File;
+import java.util.ArrayList;
+//import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.aspectj.bridge.*;
+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;
+
+/**
+ * Implement messages.
+ * This implementation is immutable if ISourceLocation is immutable,
+ * except for adding source locations.
+ */
+public class SoftMessage implements IMessage {
+ 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 String details;
+ private int id;
+ private int sourceStart,sourceEnd;
+ private final ArrayList extraSourceLocations = new ArrayList();
+
+ //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, IMessageHolder messages) {
+ if ((null == out)
+ || (null == messages)
+ || (0 == messages.numMessages(null, true))) {
+ return;
+ }
+ List list = messages.getUnmodifiableListView();
+ for (Iterator iter = list.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
+ * XXX has to sync with ajcTests.dtd
+ * @throws IllegalArgumentException if message.getThrown() is not null
+ */
+ public static void writeXml(
+ XMLWriter out,
+ IMessage message) { // XXX short form only, no files
+ if ((null == out) || (null == message)) {
+ return;
+ }
+ Throwable thrown = message.getThrown();
+ if (null != thrown) {
+ String m = "unable to write " + message + " thrown not permitted";
+ throw new IllegalArgumentException(m);
+ }
+ final String elementName = XMLNAME;
+ out.startElement(elementName, false);
+ out.printAttribute("kind", message.getKind().toString());
+ String value = message.getMessage();
+ if (null != value) {
+ value = XMLWriter.attributeValue(value);
+ out.printAttribute("text", value);
+ }
+ value = message.getDetails();
+ if (null != value) {
+ value = XMLWriter.attributeValue(value);
+ out.printAttribute("details", value);
+ }
+ ISourceLocation sl = message.getSourceLocation();
+ if (null != sl) {
+ int line = sl.getLine();
+ if (-1 < line) {
+ out.printAttribute("line", "" + line);
+ }
+ File file = sl.getSourceFile();
+ if ((null != file) && !ISourceLocation.NO_FILE.equals(file)) {
+ value = XMLWriter.attributeValue(file.getPath());
+ out.printAttribute("file", value);
+ }
+ }
+ List extras = message.getExtraSourceLocations();
+ if (!LangUtil.isEmpty(extras)) {
+ out.endAttributes();
+ for (Iterator iter = extras.iterator(); iter.hasNext();) {
+ /*ISourceLocation element = (ISourceLocation)*/ iter.next();
+ SoftSourceLocation.writeXml(out, sl);
+ }
+ }
+ 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;
+ }
+
+ public boolean isTaskTag() {
+ return kind == IMessage.TASKTAG;
+ }
+
+ /** @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;
+ }
+
+ public boolean getDeclared() { return false; }
+
+ /** @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 getSourceLocation() {
+ 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(null == getKind() ? "<null kind>" : getKind().toString());
+
+ String messageString = getMessage();
+ if (!LangUtil.isEmpty(messageString)) {
+ result.append(messageString);
+ }
+
+ ISourceLocation loc = getSourceLocation();
+ if ((null != loc) && (loc != ISourceLocation.NO_FILE)) {
+ result.append(" at " + loc);
+ }
+ if (null != thrown) {
+ result.append(" -- " + LangUtil.renderExceptionShort(thrown));
+ }
+ return result.toString();
+ }
+
+ public String getDetails() {
+ return details;
+ }
+
+ public void setDetails(String string) {
+ details = string;
+ }
+
+ public int getID() {
+ return id;
+ }
+
+ public void setID(int id) {
+ this.id = id;
+ }
+
+ public int getSourceStart() {
+ return sourceStart;
+ }
+
+ public void setSourceStart(int s) {
+ sourceStart = s;
+ }
+
+ public int getSourceEnd() {
+ return sourceStart;
+ }
+
+ public void setSourceEnd(int s) {
+ sourceEnd = s;
+ }
+
+ /* (non-Javadoc)
+ * @see org.aspectj.bridge.IMessage#getExtraSourceLocations()
+ */
+ public List getExtraSourceLocations() {
+ return extraSourceLocations;
+ }
+ public void addSourceLocation(ISourceLocation location) {
+ if (null != location) {
+ extraSourceLocations.add(location);
+ }
+ }
+}
diff --git a/testing/src/test/java/org/aspectj/testing/xml/SoftSourceLocation.java b/testing/src/test/java/org/aspectj/testing/xml/SoftSourceLocation.java
new file mode 100644
index 000000000..17f5ae673
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/xml/SoftSourceLocation.java
@@ -0,0 +1,133 @@
+/* *******************************************************************
+ * Copyright (c) 1999-2001 Xerox Corporation,
+ * 2002 Palo Alto Research Center, Incorporated (PARC),
+ * 2004 Contributors.
+ * All rights reserved.
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * Wes Isberg 2004 updates
+ * ******************************************************************/
+
+package org.aspectj.testing.xml;
+
+
+import java.io.File;
+
+import org.aspectj.bridge.ISourceLocation;
+import org.aspectj.util.LangUtil;
+
+/**
+ * A mutable ISourceLocation for XML initialization of tests.
+ * This does not support reading/writing of the attributes
+ * column, context, or endline.
+ */
+public class SoftSourceLocation implements ISourceLocation {
+ public static final File NONE = ISourceLocation.NO_FILE;
+ public static final String XMLNAME = "source";
+
+ /**
+ * Write an ISourceLocation as XML element to an XMLWriter sink.
+ * @param out the XMLWriter sink
+ * @param sl the ISourceLocation to write.
+ */
+ public static void writeXml(XMLWriter out, ISourceLocation sl) {
+ if ((null == out) || (null == sl)) {
+ return;
+ }
+ final String elementName = XMLNAME;
+ out.startElement(elementName, false);
+ out.printAttribute("line", "" + sl.getLine());
+ // other attributes not supported
+ File file = sl.getSourceFile();
+ if ((null != file) && !ISourceLocation.NO_FILE.equals(file)) {
+ String value = XMLWriter.attributeValue(file.getPath());
+ out.printAttribute("file", value);
+ }
+ out.endElement(elementName);
+ }
+
+ private File sourceFile;
+ private int line = -1; // required for no-line comparisons to work
+ private int column;
+ private int endLine;
+ private String context;
+
+ 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 String getContext() {
+ return context;
+ }
+
+ public void setFile(String sourceFile) {
+ this.sourceFile = new File(sourceFile);
+ }
+
+ public void setLine(String line) {
+ setLineAsString(line);
+ }
+
+ public void setLineAsString(String line) {
+ this.line = convert(line);
+ if (0 == endLine) {
+ endLine = this.line;
+ }
+ }
+ public String getLineAsString() {
+ return ""+line;
+ }
+
+ public void setColumn(String column) {
+ this.column = convert(column);
+ }
+
+ public void setEndLine(String line) {
+ this.endLine = convert(line);
+ }
+
+ public void setContext(String context) {
+ this.context = context;
+ }
+
+ private int convert(String in) {
+ return Integer.valueOf(in).intValue();
+ }
+
+ public String getLocationContext() {
+ return null;
+ }
+
+ public int getOffset() {
+ return -1;
+ }
+
+ /** @return String : {context\n}file:line:column */
+ public String toString() {
+ return (null == context ? "" : context + LangUtil.EOL)
+ + getSourceFile().getPath()
+ + ":" + getLine() ;
+ }
+
+ public String getSourceFileName() {
+ return null;
+ }
+}
diff --git a/testing/src/test/java/org/aspectj/testing/xml/XMLWriter.java b/testing/src/test/java/org/aspectj/testing/xml/XMLWriter.java
new file mode 100644
index 000000000..422576c56
--- /dev/null
+++ b/testing/src/test/java/org/aspectj/testing/xml/XMLWriter.java
@@ -0,0 +1,365 @@
+/* *******************************************************************
+ * 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 Eclipse Public License v1.0
+ * which accompanies this distribution and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Xerox/PARC initial implementation
+ * ******************************************************************/
+
+package org.aspectj.testing.xml;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Stack;
+
+import org.aspectj.util.LangUtil;
+
+/**
+ * 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;
+
+ /** extremely inefficient! */
+ public static String attributeValue(String input) {
+// if (-1 != input.indexOf("&amp;")) {
+// String saved = input;
+// input = LangUtil.replace(input, "&amp;", "ampamp;");
+// if (-1 == input.indexOf("&")) {
+// input = saved;
+// } else {
+// input = LangUtil.replace(input, "&", "&amp;");
+// input = LangUtil.replace(input, "ampamp;", "&amp;");
+// }
+// } else if (-1 != input.indexOf("&")) {
+// input = LangUtil.replace(input, "&", "&amp;");
+// }
+ 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;
+ }
+ }
+
+}