fr.boreal.io.dlgp.DlgpWriter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of integraal-io Show documentation
Show all versions of integraal-io Show documentation
Inputs and Outputs for integraal objects
The newest version!
package fr.boreal.io.dlgp;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import fr.boreal.io.api.Writer;
import fr.boreal.model.formula.api.FOFormula;
import fr.boreal.model.formula.api.FOFormulaConjunction;
import fr.boreal.model.formula.api.FOFormulaDisjunction;
import fr.boreal.model.formula.api.FOFormulaNegation;
import fr.boreal.model.kb.api.FactBase;
import fr.boreal.model.kb.api.KnowledgeBase;
import fr.boreal.model.kb.api.RuleBase;
import fr.boreal.model.logicalElements.api.Atom;
import fr.boreal.model.logicalElements.api.Constant;
import fr.boreal.model.logicalElements.api.FunctionalTerm;
import fr.boreal.model.logicalElements.api.Literal;
import fr.boreal.model.logicalElements.api.Predicate;
import fr.boreal.model.logicalElements.api.Substitution;
import fr.boreal.model.logicalElements.api.Term;
import fr.boreal.model.logicalElements.api.Variable;
import fr.boreal.model.query.api.FOQuery;
import fr.boreal.model.query.impl.UnionFOQuery;
import fr.boreal.model.rule.api.FORule;
/**
* Writes the content of the knowledge base as DLGP
*
* @author Florent Tornil
*/
public class DlgpWriter implements Writer {
private final java.io.Writer writer;
private boolean needLineBreak = false;
/**
* Default constructor writing in the standard output
*/
public DlgpWriter() {
this(new OutputStreamWriter(System.out) {
public void close() throws IOException {
// Do nothing (do not close System.out)
this.flush();
}
});
}
/**
* Constructor writing with the given writer
* @param writer the writer to use
*/
public DlgpWriter(java.io.Writer writer) {
this.writer = writer;
}
/**
* @param file file to write into
* @throws IOException if the file exists but is a directory rather than
* a regular file, does not exist but cannot be created,
* or cannot be opened for any other reason
*/
public DlgpWriter(File file) throws IOException {
this(new FileWriter(file));
}
/**
* @param filepath filepath to write into
* @throws IOException if the file path exists but is a directory rather than
* a regular file, does not exist but cannot be created,
* or cannot be opened for any other reason
*/
public DlgpWriter(String filepath) throws IOException {
this(new File(filepath));
}
@Override
public void close() throws IOException {
this.writer.close();
}
@Override
public void flush() throws IOException {
this.writer.flush();
}
//
// DLGP writing methods
//
@Override
public void write(Object object) throws IOException {
switch (object) {
case Term term -> this.write(term);
case KnowledgeBase knowledgeBase -> this.write(knowledgeBase);
case FactBase factBase -> this.write(factBase);
case RuleBase ruleBase -> this.write(ruleBase);
case FORule foRule -> this.write(foRule);
case FOQuery> foQuery -> this.write(foQuery);
case FOFormula foFormula -> this.write(foFormula);
case Collection> objects -> this.writeConjunction((Collection) objects);
case Predicate predicate -> this.write(predicate);
case String s -> this.write(s);
case null, default ->
throw new IllegalArgumentException("The object " + object + " of type " + object.getClass().getSimpleName() + " is not serializable as DLGP");
}
}
/**
* Write the given string
* @param str string to write
* @throws IOException If an I/O error occurs
*/
public void write(String str) throws IOException {
this.writer.write(str);
}
//
// DLGP writing methods
//
/**
* Write the given kb as DLGP
* @param kb to write
* @throws IOException If an I/O error occurs
*/
public void write(KnowledgeBase kb) throws IOException {
if(this.needLineBreak) {
this.write("\n");
this.needLineBreak = false;
}
this.write(kb.getFactBase());
this.write(kb.getRuleBase());
}
/**
* Write the given fb as DLGP
* @param fb to write
* @throws IOException If an I/O error occurs
*/
public void write(FactBase fb) throws IOException {
if(this.needLineBreak) {
this.write("\n");
this.needLineBreak = false;
}
if(fb.size() != 0) {
this.write("@facts\n");
List atoms = fb.getAtoms().collect(Collectors.toList());
this.writeConjunction(atoms);
this.write(".");
this.needLineBreak = true;
}
}
/**
* Write the given rb as DLGP
* @param rb to write
* @throws IOException If an I/O error occurs
*/
public void write(RuleBase rb) throws IOException {
if(this.needLineBreak) {
this.write("\n");
this.needLineBreak = false;
}
if(!rb.getRules().isEmpty()) {
this.write("@rules\n");
for(FORule rule : rb.getRules()) {
this.write(rule);
}
}
this.needLineBreak = true;
}
/**
* Write the given rule as DLGP
* @param rule to write
* @throws IOException If an I/O error occurs
*/
public void write(FORule rule) throws IOException {
if(this.needLineBreak) {
this.write("\n");
this.needLineBreak = false;
}
this.writeLabel(rule.getLabel());
this.write(rule.getHead());
this.write(" :- ");
this.write(rule.getBody());
this.write(".");
this.needLineBreak = true;
}
/**
* Write the given query as DLGP
* @param query to write
* @throws IOException If an I/O error occurs
*/
public void write(FOQuery> query) throws IOException {
if(query instanceof UnionFOQuery) {
throw new UnsupportedOperationException("Union of queries are not supported by DLGP");
}
if(this.needLineBreak) {
this.write("\n");
this.needLineBreak = false;
}
this.writeLabel(query.getLabel());
this.write("?");
Collection ansVars = query.getAnswerVariables();
this.write("(");
if(!ansVars.isEmpty()) {
boolean isFirst = true;
for(Variable v : ansVars) {
if(isFirst) {
isFirst = false;
} else {
this.write(", ");
}
this.write(v);
}
}
this.write(")");
this.write(" :- ");
this.write(query.getFormula());
this.writeEqualities(query.getVariableEqualities().getAssociatedSubstitution(query).orElseThrow());
this.write(".");
this.needLineBreak = true;
}
/**
* Write the given formula as DLGP
* For now, only conjunction is handled (and flattened)
* @param formula to write
* @throws IOException If an I/O error occurs
*/
public void write(FOFormula formula) throws IOException {
boolean isFirst = true;
if(formula.isConjunction()) {
for(FOFormula subformula : ((FOFormulaConjunction)formula).getSubElements()) {
if(isFirst) {
isFirst = false;
} else {
this.write(", ");
}
this.write(subformula);
}
} else if(formula.isDisjunction()) {
for(FOFormula subformula : ((FOFormulaDisjunction)formula).getSubElements()) {
if(isFirst) {
isFirst = false;
} else {
this.write("| ");
}
this.write(subformula);
}
} else if(formula.isNegation()) {
this.write("not(");
this.write(((FOFormulaNegation)formula).element());
this.write(")");
} else if(formula.isAtomic()) {
this.write((Atom)formula);
}
}
/**
* Write the given atoms as a single conjunction as DLGP
* @param atoms conjunction to write
* @throws IOException If an I/O error occurs
*/
public void writeConjunction(Collection atoms) throws IOException {
boolean isFirst = true;
for(Atom atom : atoms) {
if(isFirst) {
isFirst = false;
} else {
this.write(", ");
}
this.write(atom);
}
}
/**
* Write the given atom as DLGP
* @param atom to write
* @throws IOException If an I/O error occurs
*/
public void write(Atom atom) throws IOException {
this.write(atom.getPredicate());
this.write("(");
boolean isFirst = true;
for(Term t : atom.getTerms()) {
if(isFirst) {
isFirst = false;
} else {
this.write(", ");
}
this.write(t);
}
this.write(")");
}
/**
* Write the given predicate as DLGP
* @param p predicate to write
* @throws IOException If an I/O error occurs
*/
public void write(Predicate p) throws IOException {
String s = p.label();
if (DlgpGrammarUtils.checkLIdent(s)) {
this.write(s);
} else {
this.write("<");
this.write(encode(s));
this.write(">");
}
}
/**
* Write the given term as DLGP
* @param t term to write
* @throws IOException If an I/O error occurs
*/
public void write(Term t) throws IOException {
if(t.isVariable()) {
this.write((Variable)t);
} else if(t.isLiteral()) {
this.write((Literal>)t);
} else if(t.isFunctionalTerm()) {
this.write((FunctionalTerm)t);
} else { // Constant
this.write((Constant)t);
}
}
/**
* Write the given variable as DLGP
* @param v variable to write
* @throws IOException If an I/O error occurs
*/
public void write(Variable v) throws IOException {
String s = v.label();
char first = s.charAt(0);
if (first < 'A' || first > 'Z') {
s = "VAR_" + s;
}
s = s.replaceAll("[^a-zA-Z0-9_]", "_");
this.write(s);
}
/**
* Write the given literal as DLGP.
* This is not yet fully implemented.
*
* Actually handles Integer, Double and Boolean.
* Other types are written as String.
*
* In order to add more types, you can extend this method.
* @param l literal to write
* @throws IOException If an I/O error occurs
*/
public void write(Literal> l) throws IOException {
if (l.value() instanceof Integer) {
this.write(l.value().toString());
} else if (l.value() instanceof Double) {
this.write(l.value().toString());
} else if (l.value() instanceof Boolean) {
this.write(l.value().toString());
} else {
this.write("\"");
this.write(l.value().toString().replaceAll("\"", "\\\\\""));
this.write("\"");
}
}
/**
* Write the given functional term as DLGP.
* This is not yet fully implemented.
*
* The computed prefix is not added back !
*
* @param f functional term to write
* @throws IOException If an I/O error occurs
*/
public void write(FunctionalTerm f) throws IOException {
this.write(f.toString());
}
/**
* Write the given constant as DLGP
* @param c constant to write
* @throws IOException If an I/O error occurs
*/
public void write(Constant c) throws IOException {
String s = c.label();
if (DlgpGrammarUtils.checkLIdent(s)) {
this.write(s);
} else {
this.write("<");
this.write(encode(s));
this.write(">");
}
}
//
// Helper methods
//
/**
* Write the given DLGP label if it is not null or blank
* @param label to write as DLGP
* @throws IOException If an I/O error occurs
*/
private void writeLabel(String label) throws IOException {
if(label != null && !label.isBlank()) {
this.write("[");
this.write(label);
this.write("] ");
}
}
/**
* Write the given substitution as equalities
* This is used to convert queries with a specialization into a dlgp query
* Also assumes that this is at the end of a non-empty conjunction
* @param specialization substitution to write as equalities
* @throws IOException If an I/O error occurs
*/
private void writeEqualities(Substitution specialization) throws IOException {
for(Variable v : specialization.keys()) {
this.write(", ");
this.write(v);
this.write(" = ");
this.write(specialization.createImageOf(v));
}
}
//
// Old DLGP writer helper methods
//
private static final Map replacements = new HashMap<>();
private static final Pattern pattern;
static {
replacements.put("\\", "\\\\u00" + Integer.toHexString('\\'));
replacements.put(" ", "\\\\u00" + Integer.toHexString(' '));
replacements.put("<", "\\\\u00" + Integer.toHexString('<'));
replacements.put(">", "\\\\u00" + Integer.toHexString('>'));
replacements.put("\"", "\\\\u00" + Integer.toHexString('"'));
replacements.put("{", "\\\\u00" + Integer.toHexString('{'));
replacements.put("}", "\\\\u00" + Integer.toHexString('}'));
replacements.put("|", "\\\\u00" + Integer.toHexString('|'));
replacements.put("^", "\\\\u00" + Integer.toHexString('^'));
replacements.put("`", "\\\\u00" + Integer.toHexString('`'));
StringBuilder regexp = new StringBuilder();
boolean first = true;
for(String key : replacements.keySet()) {
if(!first) {
regexp.append('|');
}
first = false;
regexp.append("\\").append(key);
}
pattern = Pattern.compile(regexp.toString());
}
/*
* Replace some characters
*/
private static String encode(String s) {
StringBuilder sb = new StringBuilder();
Matcher m = pattern.matcher(s);
while (m.find()) {
m.appendReplacement(sb, replacements.get(m.group()));
}
m.appendTail(sb);
return sb.toString();
}
}