* Runs the checker.
*/
public void run(final FlowGraph fg, final XMLGraphBuilder b) {
cycleUnwinder = new CycleUnwinder(b.getGlobalXMLGraph().getNodes().size());
for (Statement s : fg.getNodes()) {
s.visitBy(new BasicStatementVisitor() {
@Override
public void visitCastStm(CastStm s) {
printProgress(s);
// TODO: check CastStm
}
@Override
public void visitCheckStm(CheckStm s) {
printProgress(s);
if (b.getEmptyXPathStatements().contains(s))
error(s, ErrorType.EMPTY_RESULT, "XPath expression at '%s' has empty result", s.getOpName());
if (b.getCheckFailsStatements().contains(s)) {
switch (s.getKind()) {
case GETNUMBER:
error(s, ErrorType.EMPTY_RESULT, "No digits at '%s'", s.getOpName());
break;
case GETSTRING:
error(s, ErrorType.EMPTY_RESULT, "No non-whitespace text at '%s'", s.getOpName());
break;
case HAS:
case ISATTRIBUTE:
case ISELEMENT:
case ISTEXT:
error(s, ErrorType.TEST_FAILS, "Test at '%s' always fails", s.getOpName());
break;
case TODOCUMENT:
error(s, ErrorType.NOT_UNIQUE_ROOT, "Not one root element at '%s'", s.getOpName());
break;
}
}
}
@Override
public void visitCopyStm(CopyStm s) {
printProgress(s);
// TODO: check CopyStm
}
@Override
public void visitGapifyStm(GapifyStm s) {
printProgress(s);
checkNonEmpty(s);
}
private void checkNonEmpty(Statement s) {
if (b.getEmptyXPathStatements().contains(s)) {
error(s, ErrorType.EMPTY_RESULT,
"XPath expression at '%s' has empty result",
s.getOpName());
}
}
@Override
public void visitGetStm(GetStm s) {
printProgress(s);
final XMLGraph g = b.getOut(s, s.getDest());
boolean empty;
if (g.isUnknown()) {
empty = false;
} else {
empty = true;
for (int i : g.getRoots()) {
// TODO use Emptiness instead, or upgrade sharpen() so we can just test
// if roots are empty
// true=definitely empty, false=maybe empty
empty &= g.getNode(i).process(new CachedNodeProcessor<Boolean>() {
@Override
public Boolean cycle() {
return false;
}
@Override
public Boolean process(AttributeNode n) {
return false;
}
@Override
public Boolean process(ElementNode n) {
return false;
}
@Override
public Boolean process(TextNode n) {
if (n.getText().isEmpty())
return true;
else
return false;
}
@Override
public Boolean process(OneOrMoreNode n) {
return g.getNode(n.getContent()).process(this);
}
@Override
public Boolean process(ChoiceNode n) {
if (n.isGap() && n.isOpen())
return false; // gaps are not to be considered "empty content"
else
return null;
}
@Override
public Boolean process(MultiContentNode n) {
boolean empty = true;
for (int child : n.getContents()) {
empty &= g.getNode(child).process(this);
}
return empty;
}
});
}
}
if (empty)
error(s, ErrorType.EMPTY_RESULT, "'%s' has empty result", s.getOpName());
}
@Override
public void visitInsertStm(InsertStm s) {
printProgress(s);
checkNonEmpty(s);
}
@Override
public void visitNodeStm(NodeStm s) {
printProgress(s);
// TODO: check NodeStm
}
@Override
public void visitPlugStm(final PlugStm s) {
printProgress(s);
XMLGraph g = b.getIn(s, s.getBase());
if (g.isUnknown())
return;
switch (s.getKind()) {
case PLUG:
case PLUGMULTI:
case PLUGWRAP: {
if (!g.getOpenAttributeGaps().contains(s.getGapName())
&& !g.getOpenTemplateGaps().contains(s.getGapName()))
error(s, ErrorType.MISSING_GAP, "the gap '%s' is absent", s.getGapName());
if (g.getOpenAttributeGaps().contains(s.getGapName())
&& !b.getIn(s, s.getXMLSource()).getRoots().isEmpty())
error(s, ErrorType.XML_IN_ATTRIBUTE, "maybe plugging XML data into attribute gap '%s'", s.getGapName());
final String gapType = g.getGapTypeMap().get(s.getGapName());
if (gapType != null) {
XMLGraph value_xg = b.getIn(s, s.getXMLSource());
XMLGraph type_xg = b.getGlobalXMLGraph().clone();
// SequenceNode n = b.getSchemaTypes().get(gapType);
Node n = fg.getTypemap().get(gapType);
type_xg.useFragment(new XMLGraphFragment(n, null, null, null));
Validator validator = new Validator(new ValidationErrorHandler() {
public boolean error(ElementNode n, Origin origin, String msg,
String example, Origin schema) {
if (example == null)
example = "";
XMLGraphChecker.this.error(
s,
ErrorType.INVALID_PLUG_TYPE,
"Plug statement violates the gap type %s\n" +
"because of %s created at %s\n" +
"%s %s",
gapType,
formatNode(n),
origin,
msg,
formatExample(example));
return true;
}
});
validator.validate(value_xg, type_xg, -1);
}
break;
}
case CLOSE:
if (g.getGapTypeMap().isEmpty())
return;
XMLGraph empty_xg = b.getGlobalXMLGraph().clone();
empty_xg.useFragment(new XMLGraphFragment(b.getEmptySequence(), null, null, null));
XMLGraph type_xg = b.getGlobalXMLGraph().clone();
for (final Map.Entry<String,String> gapentry : g.getGapTypeMap().entrySet()) {
final String gapName = gapentry.getKey();
final String gapType = gapentry.getValue();
Node n = fg.getTypemap().get(gapType);
type_xg.useFragment(new XMLGraphFragment(n, null, null, null));
Validator validator = new Validator(new ValidationErrorHandler() {
public boolean error(ElementNode n, Origin origin, String msg,
String example, Origin schema) {
if (example == null)
example = "";
XMLGraphChecker.this.error(
s,
ErrorType.INVALID_PLUG_TYPE,
"Close statement violates the type of gap %s\n" +
"%s does not permit an empty sequence\n" +
"Perhaps add '?' or '*' quantifier?",
gapName,
gapType);
return true;
}
});
validator.validate(empty_xg, type_xg, -1);
}
break;
}
}
@Override
public void visitRemoveStm(RemoveStm s) {
printProgress(s);
checkNonEmpty(s);
}
@Override
public void visitSetStm(SetStm s) {
printProgress(s);
checkNonEmpty(s);
}
});
}
for (Statement s : fg.getNodes()) {
s.visitBy(new BasicStatementVisitor() {
@Override
public void visitAnalyzeStm(final AnalyzeStm s) {
if (s.getKind() == AnalyzeStm.Kind.HOTSPOT)
return; // hotspots should not be analyzed here