package net.certware.argument.sfp.validation;
/**
* Semi-formal proof validation methods.
* Hooked into Xtext validator through extension point and abstract class code generation.
* Uses dependency injection to identify methods to invoke.
* @author mrb
* @since 1.0.3
*/
import java.util.HashSet;
import java.util.Set;
import net.certware.argument.sfp.semiFormalProof.Entailment;
import net.certware.argument.sfp.semiFormalProof.Justification;
import net.certware.argument.sfp.semiFormalProof.Justifications;
import net.certware.argument.sfp.semiFormalProof.Proof;
import net.certware.argument.sfp.semiFormalProof.ProofSteps;
import net.certware.argument.sfp.semiFormalProof.SemiFormalProofPackage;
import net.certware.argument.sfp.semiFormalProof.Statement;
import net.certware.argument.sfp.util.Graph;
import net.certware.argument.sfp.util.GraphAlgs;
import net.certware.argument.sfp.util.ProofUtil;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.mwe2.language.validation.AbstractMwe2JavaValidator;
import org.eclipse.xtext.validation.Check;
/**
* Validation for the semi-formal proof model.
* Uses the Xtext generated abstract validator and check annotations.
* @author mrb
* @since 1.0.3
*/
public class SemiFormalProofJavaValidator extends AbstractMwe2JavaValidator {
/**
* Justification must have one of its parts.
* Issues error of no assertion, entailment, or numeral set in the justification.
* @param justification justification to validate
*/
@Check
public void checkJustificationNonEmpty(Justification justification) {
if (justification.getAssertion().getText().isEmpty() &&
justification.getEntailment().eContents().isEmpty() &&
justification.getNumeral().isEmpty() ) {
error("Justification has no assertion, entailment, or numeral",
SemiFormalProofPackage.Literals.JUSTIFICATION__ENTAILMENT);
}
}
/**
* A proof must have at least one proof step.
* Issues error if proof statements list is empty.
* @param proof proof to validate
*/
@Check
public void proofHasProofSteps(Proof proof) {
if ( proof.getProofSteps().getStatements().isEmpty() ) {
error(String.format("Proof %s has no proof steps", proof.getTitle() ),
SemiFormalProofPackage.Literals.PROOF__PROOF_STEPS);
}
}
// TODO public void proofHasUnusedSteps(Proof proof)
/**
* A proof step statement's justification cannot refer to itself.
* Issues warning if statement ID is in the justification numeral list.
* @param statement statement to validate
*/
@Check
public void statementJustificationRefersToSelf(Statement statement) {
if ( statement.getJustification() == null )
return;
Justifications justifications = statement.getJustification();
if ( justifications == null || justifications.getJustifications().isEmpty() )
return;
String id = statement.getId();
for ( Justification j : justifications.getJustifications() ) {
if ( id.equalsIgnoreCase(j.getNumeral())) {
warning(String.format("Statement %s justification refers to itself", id),
SemiFormalProofPackage.Literals.JUSTIFICATIONS__JUSTIFICATIONS);
}
}
}
/**
* Justification numerical reference must refer to a statement ID.
* Issues error if statement ID not found in containing proof.
* @param j justification to validate
*/
@Check
public void statementJustificationNumeralValid(Justification j) {
if ( j.getNumeral() != null ) {
Proof proof = (Proof)EcoreUtil.getRootContainer(j);
ProofSteps ps = proof.getProofSteps();
boolean found = false;
for ( Statement s : ps.getStatements() ) {
if ( s.getId().equalsIgnoreCase( j.getNumeral() )) {
found = true;
break;
}
}
if ( found == false ) {
error(String.format("Justification refers to missing statement %s",j.getNumeral()),
SemiFormalProofPackage.Literals.JUSTIFICATION__NUMERAL);
}
}
}
/**
* Entailment tail must refer to valid statement ID.
* Issues warning if tail ID not found in statement list.
* @param entailment entailment to validate
*/
@Check
public void entailmentTailExists(Entailment entailment) {
String tail = entailment.getTail();
Proof proof = (Proof)EcoreUtil.getRootContainer(entailment);
if ( proof.getProofSteps() == null )
return;
boolean found = false;
for ( Statement s : proof.getProofSteps().getStatements() ) {
if ( tail.equalsIgnoreCase(s.getId() )) {
found = true;
break;
}
}
if ( found == false ) {
warning(String.format("Entailment tail refers to missing statement %s",tail),
SemiFormalProofPackage.Literals.ENTAILMENT__TAIL);
}
}
/**
* Entailment head must refer to valid statement IDs.
* Issues warning for each head ID not found in statement list.
* @param entailment entailment to validate
*/
@Check
public void entailmentHeadExists(Entailment entailment) {
EList<String> heads = ProofUtil.getHeadList(entailment);
Proof proof = (Proof)EcoreUtil.getRootContainer(entailment);
if ( proof.getProofSteps() == null )
return;
boolean found = false;
for ( String id : heads ) {
for ( Statement s : proof.getProofSteps().getStatements() ) {
if ( id.equalsIgnoreCase(s.getId() )) {
found = true;
break;
}
}
if ( found == false ) {
warning(String.format("Entailment head refers to missing statement %s",id),
SemiFormalProofPackage.Literals.ENTAILMENT__HEAD);
}
}
}
/**
* An entailment head or tail cannot include the statement of its declaration.
* Issues warning if statement ID is in the entailment head or tail.
* @param statement statement to validate
*/
@Check
public void statementWithinEntailment(Statement statement) {
if ( statement.getJustification() == null )
return;
Justifications justifications = statement.getJustification();
if ( justifications == null || justifications.getJustifications().isEmpty() )
return;
String id = statement.getId();
for ( Justification j : justifications.getJustifications() ) {
if ( j.getEntailment() == null ) {
continue;
}
Entailment e = j.getEntailment();
if ( ProofUtil.isTail(e,id) ) {
warning(String.format("Statement %s entailment tail refers to itself", id),
SemiFormalProofPackage.Literals.ENTAILMENT__TAIL);
}
if ( ProofUtil.isInHead(e,id) ) {
warning(String.format("Statement %s entailment head contains itself", id),
SemiFormalProofPackage.Literals.ENTAILMENT__HEAD);
}
}
}
/**
* The proof step references must be acyclic.
* Issues error if proof statement references contain cycles.
* @param proof proof to validate
*/
@Check
public void proofHasNoCycles(Proof proof) {
if ( proof == null || proof.getProofSteps() == null )
return;
EList<Statement> statements = proof.getProofSteps().getStatements();
if ( statements == null || statements.isEmpty() ) {
return;
}
// create a graph with number of vertices equal to statement list count
int statementCount = statements.size();
Graph graph = new Graph(statementCount,true);
// load the graph edges according to justification references
boolean edgeInsertionClean = true;
try {
for ( Statement s : statements ) {
graph.insert(s);
}
} catch( ArrayIndexOutOfBoundsException obe) {
edgeInsertionClean = false;
}
// now search the graph for cycles
if ( edgeInsertionClean ) {
String result = graph.isAcyclic();
if ( result != null ) {
warning(String.format("%s %s", "Proof:", result),
SemiFormalProofPackage.Literals.PROOF__PROOF_STEPS);
return;
}
// acyclic, so now check the entailment structure by statement
for ( Statement s : statements ) {
Set<String> context = new HashSet<String>();
GraphAlgs.checkEntailments(proof, s, context);
if ( context.isEmpty() == false ) {
warning(String.format("Entailment invalid at statement %s context %s",
s.getId(),
context.toString()),
SemiFormalProofPackage.Literals.PROOF__PROOF_STEPS);
} // if
} // for
} else {
// System.err.println("Graph is unclean for validation of cycles");
} // clean
}
}