Completely new (clearer?) message-checking code.tags/mostlyLastEclipse2xTree_20040112
@@ -30,298 +30,314 @@ 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 { | |||
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; | |||
/** Comparator for enclosed IMessage diffs */ | |||
public static final Comparator COMP_IMessage = | |||
BridgeUtil.Comparators.MEDIUM_IMessage; | |||
/** 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; | |||
/** Comparator for enclosed File diffs */ | |||
public static final Comparator COMP_File = BridgeUtil.Comparators.WEAK_File; | |||
/** true if there were no error or worse messages expected */ | |||
private final boolean expectingCommandTrue; | |||
/** unmodifiable list of IMessage messages of any type */ | |||
private final List expectedMessagesAsList; | |||
/** 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; | |||
} | |||
/** IMessageHolder variant of expectedMessagesAsList */ | |||
private final IMessageHolder expectedMessages; | |||
/** | |||
* 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 expected; | |||
final List 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 | |||
Diffs messages = new Diffs( | |||
"message", | |||
expected, | |||
actual, | |||
COMP_IMessage, | |||
Diffs.ACCEPT_ALL, | |||
CompilerDiffs.SKIP_UNEXPECTED_INFO); | |||
Diffs recompiled = new Diffs( | |||
"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); | |||
} | |||
/** number of messages FAIL or worse */ | |||
private final int numExpectedFailed; | |||
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 | |||
} | |||
/** true if there were no error or worse messages expected */ | |||
private final boolean expectingCommandTrue; | |||
/** | |||
* 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; | |||
} | |||
/** unmodifiable list of File expected to be recompiled */ | |||
private final List expectedRecompiled; | |||
// Unused now, but reinstate when supported | |||
private IMessage[] getMessagesWithoutExpectedFails() { | |||
IMessage[] result = super.getMessages(null, true); | |||
// remove all expected fail+ (COSTLY) | |||
ArrayList 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]); | |||
/** 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 expected; | |||
final List 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); | |||
} | |||
} | |||
result = (IMessage[]) list.toArray(new IMessage[0]); | |||
return result; | |||
} | |||
Diffs recompiled = | |||
Diffs.makeDiffs( | |||
"recompiled", | |||
expectedRecompiled, | |||
actualRecompiled, | |||
COMP_File); | |||
diffs = new CompilerDiffs(messages, recompiled); | |||
} | |||
return diffs; | |||
} | |||
/** | |||
* @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); | |||
} | |||
} | |||
/** | |||
* 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 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); | |||
} | |||
} | |||
} |
@@ -24,6 +24,7 @@ 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; | |||
@@ -368,7 +369,7 @@ public class FlatSuiteReader implements SFileReader.Maker { | |||
abortOnError, | |||
System.err); | |||
} catch (IOException e) { | |||
IMessage m = Message.fail("reading " + suiteFile, e); | |||
IMessage m = MessageUtil.fail("reading " + suiteFile, e); | |||
throw new AbortException(m); | |||
} | |||
@@ -13,6 +13,7 @@ | |||
package org.aspectj.testing.util; | |||
import java.io.File; | |||
import java.util.*; | |||
import java.util.ArrayList; | |||
import java.util.Collections; | |||
@@ -22,120 +23,525 @@ 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 */ | |||
/** | |||
* Result struct for expected/actual diffs for Collection | |||
*/ | |||
public class Diffs { | |||
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); | |||
/** name of the thing being diffed - used only for reporting */ | |||
public final String label; | |||
/** immutable List */ | |||
public final List missing; | |||
/** | |||
* 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); | |||
} | |||
}; | |||
/** immutable List */ | |||
public final List unexpected; | |||
/** | |||
* 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); | |||
/** true if there are any missing or unexpected */ | |||
public final boolean different; | |||
private Diffs(String label, List missing, List unexpected) { | |||
this.label = label; | |||
this.missing = missing; | |||
this.unexpected = unexpected; | |||
different = ((0 != this.missing.size()) | |||
|| (0 != this.unexpected.size())); | |||
} | |||
public static Diffs makeDiffs( | |||
String label, | |||
List expected, | |||
List actual, | |||
Comparator comparator) { | |||
return makeDiffs( | |||
label, | |||
expected, | |||
actual, | |||
comparator, | |||
ACCEPT_ALL, | |||
ACCEPT_ALL); | |||
} | |||
public Diffs( | |||
String label, | |||
List expected, | |||
List actual, | |||
Comparator comparator) { | |||
this(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); | |||
} | |||
public Diffs( | |||
String label, | |||
List expected, | |||
List actual, | |||
Comparator comparator, | |||
Filter missingFilter, | |||
Filter unexpectedFilter) { | |||
label = label.trim(); | |||
if (null == label) { | |||
label = ": "; | |||
} else if (!label.endsWith(":")) { | |||
label += ": "; | |||
} | |||
this.label = " " + label; | |||
ArrayList miss = new ArrayList(); | |||
ArrayList unexpect = new ArrayList(); | |||
org.aspectj.testing.util.LangUtil.makeSoftDiffs(expected, actual, miss, unexpect, comparator); | |||
if (null != missingFilter) { | |||
for (ListIterator iter = miss.listIterator(); iter.hasNext();) { | |||
if (!missingFilter.accept(iter.next())) { | |||
iter.remove(); | |||
} | |||
} | |||
} | |||
missing = Collections.unmodifiableList(miss); | |||
if (null != unexpectedFilter) { | |||
for (ListIterator iter = unexpect.listIterator(); iter.hasNext();) { | |||
if (!unexpectedFilter.accept(iter.next())) { | |||
iter.remove(); | |||
} | |||
} | |||
private static int getLine(ISourceLocation loc) { | |||
int result = -1; | |||
if (null != loc) { | |||
result = loc.getLine(); | |||
} | |||
unexpected = Collections.unmodifiableList(unexpect); | |||
different = ((0 != this.missing.size()) | |||
|| (0 != this.unexpected.size())); | |||
return result; | |||
} | |||
/** | |||
* 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); | |||
} | |||
private static int getLine(IMessage message) { | |||
int result = -1; | |||
if ((null != message)) { | |||
result = getLine(message.getSourceLocation()); | |||
} | |||
return result; | |||
} | |||
/** @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); | |||
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); | |||
} | |||
} |
@@ -193,7 +193,7 @@ public class FileUtil { | |||
unexp.addAll(Arrays.asList(dir.listFiles(touchedCollector))); | |||
// report any unexpected changes | |||
return new Diffs(label, expected, unexp, String.CASE_INSENSITIVE_ORDER); | |||
return Diffs.makeDiffs(label, expected, unexp, String.CASE_INSENSITIVE_ORDER); | |||
} | |||
@@ -270,8 +270,7 @@ public class LangUtil { | |||
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. |
@@ -80,13 +80,13 @@ public class TestDiffs { // XXX pretty dumb implementation | |||
reading = actual; | |||
act = TestDiffs.readTestResults(actual, actual.getPath()); | |||
Diffs tests = new Diffs("tests", exp, act, TestResult.BY_NAME); | |||
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 = new Diffs("results", expResults, actResults, TestResult.BY_PASSNAME); | |||
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 |
@@ -52,6 +52,13 @@ public class AjcSpecXmlReader { | |||
* - 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"; | |||
@@ -63,82 +70,6 @@ public class AjcSpecXmlReader { | |||
public static final String DOCTYPE = "<!DOCTYPE " | |||
+ AjcTest.Suite.Spec.XMLNAME + " SYSTEM \"" + DTD_PATH + "\">"; | |||
/** xml leader */ | |||
public static final String FILE_LEADER | |||
= "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"; | |||
/** | |||
* @deprecated | |||
* @return a String suitable as an inlined DOCTYPE statement | |||
*/ | |||
public static String inlineDocType() { | |||
return "<!DOCTYPE " | |||
+ AjcTest.Suite.Spec.XMLNAME | |||
+ " [" | |||
+ AjcSpecXmlReader.getDocType() | |||
+ EOL + " ]>"; | |||
} | |||
/** | |||
* @deprecated | |||
* @return the elements of a document type as a String, | |||
* using EOL as a line delimiter | |||
*/ | |||
public static String getDocType() { | |||
if (true) { | |||
throw new Error("XXX using ajcTestSuite.dtd"); | |||
} | |||
StringBuffer r = new StringBuffer(); | |||
final String suiteX = AjcTest.Suite.Spec.XMLNAME; | |||
final String ajctestX = AjcTest.Spec.XMLNAME; | |||
final String compileX = CompilerRun.Spec.XMLNAME; | |||
final String inccompileX = IncCompilerRun.Spec.XMLNAME; | |||
final String runX = JavaRun.Spec.XMLNAME; | |||
final String dirchangesX = DirChanges.Spec.XMLNAME; | |||
final String messageX = SoftMessage.XMLNAME; | |||
r.append(EOL + " <!ELEMENT " + suiteX + " (" + ajctestX + "+)>"); | |||
r.append(EOL + " <!ATTLIST " + suiteX + " suiteDir CDATA #IMPLIED >"); | |||
r.append(EOL + ""); | |||
r.append(EOL + " <!ELEMENT " + ajctestX + " (" + compileX + ", (" + compileX + " | " + inccompileX + " | " + runX + ")*)>"); | |||
r.append(EOL + " <!ATTLIST " + ajctestX + " title CDATA #REQUIRED >"); | |||
r.append(EOL + " <!ATTLIST " + ajctestX + " dir CDATA #REQUIRED >"); | |||
r.append(EOL + " <!ATTLIST " + ajctestX + " pr CDATA #IMPLIED >"); | |||
r.append(EOL + " <!ATTLIST " + ajctestX + " keywords CDATA #IMPLIED >"); | |||
r.append(EOL + ""); | |||
r.append(EOL + " <!ELEMENT " + compileX + " (" + dirchangesX + "*,file*," + messageX + "*)>"); // deprecate file? | |||
r.append(EOL + " <!ATTLIST " + compileX + " staging CDATA #IMPLIED >"); // if precursor to incremental | |||
r.append(EOL + " <!ATTLIST " + compileX + " files CDATA #IMPLIED >"); | |||
r.append(EOL + " <!ATTLIST " + compileX + " options CDATA #IMPLIED >"); | |||
r.append(EOL + ""); | |||
r.append(EOL + " <!ELEMENT " + inccompileX + " (" + dirchangesX + "*," + messageX + "*)>"); // add file* if not deprecated | |||
r.append(EOL + " <!ATTLIST " + inccompileX + " tag CDATA #REQUIRED >"); | |||
r.append(EOL + ""); | |||
r.append(EOL + " <!ELEMENT " + runX + " (" + dirchangesX + "*," + messageX + "*)>"); | |||
r.append(EOL + " <!ATTLIST " + runX + " class CDATA #REQUIRED >"); | |||
r.append(EOL + " <!ATTLIST " + runX + " skipTester CDATA #IMPLIED >"); | |||
r.append(EOL + " <!ATTLIST " + runX + " options CDATA #IMPLIED >"); | |||
r.append(EOL + ""); | |||
r.append(EOL + " <!ELEMENT file (#PCDATA)>"); // deprecate? | |||
r.append(EOL + " <!ATTLIST file path CDATA #IMPLIED >"); | |||
r.append(EOL + ""); | |||
r.append(EOL + " <!ELEMENT " + messageX + " (#PCDATA)>"); | |||
r.append(EOL + " <!ATTLIST " + messageX + " kind (error | warning | info | Xlint) #REQUIRED >"); | |||
r.append(EOL + " <!ATTLIST " + messageX + " line CDATA #REQUIRED >"); | |||
r.append(EOL + " <!ATTLIST " + messageX + " text CDATA #IMPLIED >"); // but Message requires non-null... | |||
r.append(EOL + " <!ATTLIST " + messageX + " file CDATA #IMPLIED >"); | |||
r.append(EOL + ""); | |||
r.append(EOL + " <!ELEMENT " + dirchangesX + " (#PCDATA)>"); | |||
r.append(EOL + " <!ATTLIST " + dirchangesX + " dirToken (classes | run) #IMPLIED >"); | |||
r.append(EOL + " <!ATTLIST " + dirchangesX + " defaultSuffix (.class) #IMPLIED >"); | |||
r.append(EOL + " <!ATTLIST " + dirchangesX + " added CDATA #IMPLIED >"); | |||
r.append(EOL + " <!ATTLIST " + dirchangesX + " removed CDATA #IMPLIED >"); | |||
r.append(EOL + " <!ATTLIST " + dirchangesX + " updated CDATA #IMPLIED >"); | |||
r.append(EOL + " <!ATTLIST " + dirchangesX + " unchanged CDATA #IMPLIED >"); | |||
r.append(EOL + ""); | |||
return r.toString(); | |||
} | |||
private static final AjcSpecXmlReader ME | |||
= new AjcSpecXmlReader(); | |||
@@ -162,7 +93,7 @@ public class AjcSpecXmlReader { | |||
try { | |||
out.println("<!-- document type for ajc test suite - see " | |||
+ AjcSpecXmlReader.class.getName() + " -->"); | |||
out.println(getDocType()); | |||
//out.println(getDocType()); | |||
} finally { | |||
out.close(); | |||
} | |||
@@ -170,7 +101,8 @@ public class AjcSpecXmlReader { | |||
private static final String[] LOG = new String[] {"info", "debug", "trace" }; | |||
private int logLevel; | |||
// XXX logLevel n>0 causes JUnit tests to fail! | |||
private int logLevel = 0; // use 2 for tracing | |||
private AjcSpecXmlReader() {} | |||
@@ -292,7 +224,7 @@ public class AjcSpecXmlReader { | |||
final String runX = ajctestX + "/" + JavaRun.Spec.XMLNAME; | |||
final String dirchangesX = "*/" + DirChanges.Spec.XMLNAME; | |||
final String messageX = "*/" + SoftMessage.XMLNAME; | |||
final String messageSrcLocX = messageX + "/source-location"; | |||
final String messageSrcLocX = messageX + "/" +SoftSourceLocation.XMLNAME; | |||
// ---- each sub-element needs to be created | |||
// handle messages the same at any level | |||
@@ -323,9 +255,10 @@ public class AjcSpecXmlReader { | |||
new String[] { "className", "javaVersion", "skipTester"}); | |||
digester.addSetProperties(dirchangesX); | |||
digester.addSetProperties(messageX); | |||
digester.addSetProperties(messageSrcLocX); | |||
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); | |||
@@ -340,7 +273,9 @@ public class AjcSpecXmlReader { | |||
digester.addSetNext(runX, "addChild", JavaRun.Spec.class.getName()); | |||
digester.addSetNext(compileX + "/file", "addWrapFile", AbstractRunSpec.WrapFile.class.getName()); | |||
digester.addSetNext(messageX, "addMessage", IMessage.class.getName()); | |||
digester.addSetNext(messageSrcLocX, "setSourceLocation", ISourceLocation.class.getName()); | |||
// 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... | |||
@@ -372,7 +307,7 @@ public class AjcSpecXmlReader { | |||
new BProps(AbstractRunSpec.WrapFile.class, | |||
new String[] { "path"}), | |||
new BProps(SoftMessage.class, | |||
new String[] { "kindAsString", "lineAsString", "text", "file"}) | |||
new String[] { "kindAsString", "lineAsString", "text", "details", "file"}) | |||
// mapped from { "kind", "line", ...} | |||
}; | |||
} | |||
@@ -430,6 +365,7 @@ public class AjcSpecXmlReader { | |||
m.setSourceLocation((ISourceLocation) null); | |||
m.setText((String) null); | |||
m.setKindAsString((String) null); | |||
m.setDetails((String) null); | |||
SoftSourceLocation sl = new SoftSourceLocation(); | |||
sl.setFile((String) null); |
@@ -11,10 +11,10 @@ | |||
* 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; | |||
@@ -26,261 +26,294 @@ 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. | |||
* This implementation is immutable if ISourceLocation is immutable, | |||
* except for adding source locations. | |||
*/ | |||
public class SoftMessage implements IMessage { // XXX mutable dup of Message | |||
public static String XMLNAME = "message"; | |||
public static final File NO_FILE = ISourceLocation.NO_FILE; | |||
private String message; | |||
private IMessage.Kind kind; | |||
private Throwable thrown; | |||
private ISourceLocation sourceLocation; | |||
private String details; | |||
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 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 | |||
* @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) { | |||
out.printAttribute("message", value); | |||
} | |||
ISourceLocation sl = message.getSourceLocation(); | |||
if (null != sl) { | |||
out.endAttributes(); | |||
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"); | |||
// 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("message", 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); | |||
} | |||
} | |||
if (null == kind) { | |||
throw new IllegalArgumentException("null kind"); | |||
} | |||
} | |||
/** @return the kind of this message */ | |||
public IMessage.Kind getKind() { | |||
return kind; | |||
} | |||
/** @return true if kind == IMessage.ERROR */ | |||
public boolean isError() { | |||
return kind == IMessage.ERROR; | |||
} | |||
/** @return true if kind == IMessage.WARNING */ | |||
public boolean isWarning() { | |||
return kind == IMessage.WARNING; | |||
} | |||
/** @return true if kind == IMessage.DEBUG */ | |||
public boolean isDebug() { | |||
return kind == IMessage.DEBUG; | |||
} | |||
/** | |||
* @return true if kind == IMessage.INFO | |||
*/ | |||
public boolean isInfo() { | |||
return kind == IMessage.INFO; | |||
} | |||
/** @return true if kind == IMessage.ABORT */ | |||
public boolean isAbort() { | |||
return kind == IMessage.ABORT; | |||
} | |||
/** | |||
* @return true if kind == IMessage.FAIL | |||
*/ | |||
public boolean isFailed() { | |||
return kind == IMessage.FAIL; | |||
} | |||
/** @return non-null String with simple message */ | |||
final public String getMessage() { | |||
return message; | |||
} | |||
/** @return Throwable associated with this message, or null if none */ | |||
final public Throwable getThrown() { | |||
return thrown; | |||
} | |||
/** | |||
* This returns any ISourceLocation set or a mock-up | |||
* if file and/or line were set. | |||
* @return ISourceLocation associated with this message, | |||
* a mock-up if file or line is available, or null if none | |||
*/ | |||
final public ISourceLocation 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(getKind().toString()); | |||
String messageString = getMessage(); | |||
if (!LangUtil.isEmpty(messageString)) { | |||
result.append(messageString); | |||
} | |||
out.endElement(elementName); | |||
} | |||
public SoftMessage() { | |||
} // XXX programmatic only | |||
/** | |||
* Create a (compiler) error or warning message | |||
* @param message the String used as the underlying message | |||
* @param sourceLocation the ISourceLocation, if any, associated with this message | |||
* @param isError if true, use IMessage.ERROR; else use IMessage.WARNING | |||
*/ | |||
public SoftMessage( | |||
String message, | |||
ISourceLocation location, | |||
boolean isError) { | |||
this( | |||
message, | |||
(isError ? IMessage.ERROR : IMessage.WARNING), | |||
null, | |||
location); | |||
} | |||
/** | |||
* Create a message, handling null values for message and kind | |||
* if thrown is not null. | |||
* @param message the String used as the underlying message | |||
* @param kind the IMessage.Kind of message - not null | |||
* @param thrown the Throwable, if any, associated with this message | |||
* @param sourceLocation the ISourceLocation, if any, associated with this message | |||
* @throws IllegalArgumentException if message is null and | |||
* thrown is null or has a null message, or if kind is null | |||
* and thrown is null. | |||
*/ | |||
public SoftMessage( | |||
String message, | |||
IMessage.Kind kind, | |||
Throwable thrown, | |||
ISourceLocation sourceLocation) { | |||
this.message = message; | |||
this.kind = kind; | |||
this.thrown = thrown; | |||
this.sourceLocation = sourceLocation; | |||
if (null == message) { | |||
if (null != thrown) { | |||
message = thrown.getMessage(); | |||
} | |||
if (null == message) { | |||
throw new IllegalArgumentException("null message"); | |||
} | |||
} | |||
if (null == kind) { | |||
throw new IllegalArgumentException("null kind"); | |||
} | |||
} | |||
/** @return the kind of this message */ | |||
public IMessage.Kind getKind() { | |||
return kind; | |||
} | |||
/** @return true if kind == IMessage.ERROR */ | |||
public boolean isError() { | |||
return kind == IMessage.ERROR; | |||
} | |||
/** @return true if kind == IMessage.WARNING */ | |||
public boolean isWarning() { | |||
return kind == IMessage.WARNING; | |||
} | |||
/** @return true if kind == IMessage.DEBUG */ | |||
public boolean isDebug() { | |||
return kind == IMessage.DEBUG; | |||
} | |||
/** | |||
* @return true if kind == IMessage.INFO | |||
*/ | |||
public boolean isInfo() { | |||
return kind == IMessage.INFO; | |||
} | |||
/** @return true if kind == IMessage.ABORT */ | |||
public boolean isAbort() { | |||
return kind == IMessage.ABORT; | |||
} | |||
/** | |||
* @return true if kind == IMessage.FAIL | |||
*/ | |||
public boolean isFailed() { | |||
return kind == IMessage.FAIL; | |||
} | |||
/** @return non-null String with simple message */ | |||
final public String getMessage() { | |||
return message; | |||
} | |||
/** @return Throwable associated with this message, or null if none */ | |||
final public Throwable getThrown() { | |||
return thrown; | |||
} | |||
/** | |||
* This returns any ISourceLocation set or a mock-up | |||
* if file and/or line were set. | |||
* @return ISourceLocation associated with this message, | |||
* a mock-up if file or line is available, or null if none | |||
*/ | |||
final public ISourceLocation 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(); | |||
} | |||
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; | |||
} | |||
@@ -293,6 +326,11 @@ public class SoftMessage implements IMessage { // XXX mutable dup of Message | |||
* @see org.aspectj.bridge.IMessage#getExtraSourceLocations() | |||
*/ | |||
public List getExtraSourceLocations() { | |||
return Collections.EMPTY_LIST; | |||
return extraSourceLocations; | |||
} | |||
public void addSourceLocation(ISourceLocation location) { | |||
if (null != location) { | |||
extraSourceLocations.add(location); | |||
} | |||
} | |||
} |
@@ -1,6 +1,7 @@ | |||
/* ******************************************************************* | |||
* Copyright (c) 1999-2001 Xerox Corporation, | |||
* 2002 Palo Alto Research Center, Incorporated (PARC). | |||
* 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 Common Public License v1.0 | |||
@@ -9,6 +10,7 @@ | |||
* | |||
* Contributors: | |||
* Xerox/PARC initial implementation | |||
* Wes Isberg 2004 updates | |||
* ******************************************************************/ | |||
package org.aspectj.testing.xml; | |||
@@ -20,17 +22,13 @@ import org.aspectj.bridge.ISourceLocation; | |||
import org.aspectj.util.LangUtil; | |||
/** | |||
* Immutable source location. | |||
* This guarantees that the source file is not null | |||
* and that the numeric values are positive and line <= endLine. | |||
* @see org.aspectj.lang.reflect.SourceLocation | |||
* @see org.aspectj.compiler.base.parser.SourceInfo | |||
* @see org.aspectj.tools.ide.SourceLine | |||
* @see org.aspectj.testing.harness.ErrorLine | |||
* 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 { // XXX endLine? | |||
public static final File NONE = new File("SoftSourceLocation.NONE"); | |||
public static final String XMLNAME = "source-location"; | |||
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. | |||
@@ -44,17 +42,16 @@ public class SoftSourceLocation implements ISourceLocation { // XXX endLine? | |||
final String elementName = XMLNAME; | |||
out.startElement(elementName, false); | |||
out.printAttribute("line", "" + sl.getLine()); | |||
out.printAttribute("column", "" + sl.getColumn()); | |||
out.printAttribute("endLine", "" + sl.getEndLine()); | |||
// other attributes not supported | |||
File file = sl.getSourceFile(); | |||
if (null != file) { | |||
out.printAttribute("sourceFile", file.getPath()); | |||
out.printAttribute("file", file.getPath()); | |||
} | |||
out.endElement(elementName); | |||
} | |||
private File sourceFile; | |||
private int line; | |||
private int line = -1; // required for no-line comparisons to work | |||
private int column; | |||
private int endLine; | |||
private String context; | |||
@@ -83,13 +80,20 @@ public class SoftSourceLocation implements ISourceLocation { // XXX endLine? | |||
public void setFile(String sourceFile) { | |||
this.sourceFile = new File(sourceFile); | |||
} | |||
public void setLine(String line) { | |||
setLineAsString(line); | |||
} | |||
public void setLine(String 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); | |||
@@ -115,7 +119,6 @@ public class SoftSourceLocation implements ISourceLocation { // XXX endLine? | |||
public String toString() { | |||
return (null == context ? "" : context + LangUtil.EOL) | |||
+ getSourceFile().getPath() | |||
+ ":" + getLine() | |||
+ ":" + getColumn(); | |||
+ ":" + getLine() ; | |||
} | |||
} |