blob: 2873761fc2f8046f36060b65e2ba996639d94af0 [file] [log] [blame]
package org.apache.rya.reasoning;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.apache.hadoop.io.WritableComparable;
import org.apache.rya.api.domain.RyaStatement;
import org.apache.rya.api.resolver.RyaToRdfConversions;
import org.apache.rya.api.utils.LiteralLanguageUtils;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.model.vocabulary.RDF;
/**
* Represents a fact used and/or generated by the reasoner.
*/
public class Fact implements WritableComparable<Fact>, Cloneable {
private static final ValueFactory VF = SimpleValueFactory.getInstance();
Statement triple;
// If this is a derived fact:
Derivation derivation;
// Potentially useful for future derivations
boolean useful = true;
// An empty fact
public static final Fact NONE = new Fact();
private static final String SEP = "\u0000";
/**
* Default constructor, contains no information
*/
public Fact() { }
/**
* A fact containing a triple and no generating rule.
*/
public Fact(final Statement stmt) {
this.triple = stmt;
}
/**
* A fact containing a triple and no generating rule.
*/
public Fact(final Resource s, final IRI p, final Value o) {
this.triple = VF.createStatement(s, p, o);
}
/**
* A fact which contains a triple and was generated using a
* particular rule by a reasoner for a particular node.
*/
public Fact(final Resource s, final IRI p, final Value o, final int iteration,
final OwlRule rule, final Resource node) {
this.triple = VF.createStatement(s, p, o);
this.derivation = new Derivation(iteration, rule, node);
}
public Statement getTriple() {
return triple;
}
public Resource getSubject() {
if (triple == null) {
return null;
}
else {
return triple.getSubject();
}
}
public IRI getPredicate() {
if (triple == null) {
return null;
}
else {
return triple.getPredicate();
}
}
public Value getObject() {
if (triple == null) {
return null;
}
else {
return triple.getObject();
}
}
/**
* Get the derivation if it exists, or the empty derivation otherwise.
*/
public Derivation getDerivation() {
if (derivation == null) {
return Derivation.NONE;
}
else {
return derivation;
}
}
public boolean isInference() {
return derivation != null;
}
public boolean isUseful() {
return useful;
}
public boolean isEmpty() {
return triple == null;
}
/**
* Assign a particular statement to this fact.
*/
public void setTriple(final Statement stmt) {
triple = stmt;
}
/**
* Assign a particular statement to this fact.
*/
public void setTriple(final RyaStatement rs) {
setTriple(RyaToRdfConversions.convertStatement(rs));
}
/**
* Set a flag if this triple *could* be used in future derivations
* (may only actually happen if certain other facts are seen as well.)
*/
public void setUseful(final boolean useful) {
this.useful = useful;
}
/**
* Set derivation. Allows reconstructing a fact and the way it was produced.
*/
public void setDerivation(final Derivation d) {
this.derivation = d;
}
/**
* Set derivation to null and return its former value. Allows decoupling
* of the fact from the way it was produced.
*/
public Derivation unsetDerivation() {
final Derivation d = getDerivation();
this.derivation = null;
return d;
}
/**
* Generate a String showing this fact's derivation.
* @param multiline Print a multi-line tree as opposed to a nested list
* @param schema Use schema knowledge to further explain BNodes
*/
public String explain(final boolean multiline, final Schema schema) {
return explain(multiline, "", schema);
}
/**
* Generate a String showing this fact's derivation. Does not incorporate
* schema information.
* @param multiline Print a multi-line tree as opposed to a nested list
*/
public String explain(final boolean multiline) {
return explain(multiline, "", null);
}
/**
* Recursively generate a String to show this fact's derivation.
*/
String explain(final boolean multiline, final String prefix, final Schema schema) {
final StringBuilder sb = new StringBuilder();
String sep = " ";
if (multiline) {
sep = "\n" + prefix;
}
if (triple == null) {
sb.append("(empty)").append(sep);
}
else {
final Resource s = getSubject();
final IRI p = getPredicate();
final Value o = getObject();
sb.append("<").append(s.toString()).append(">").append(sep);
sb.append("<").append(p.toString()).append(">").append(sep);
sb.append("<").append(o.toString()).append(">");
// Restrictions warrant further explanation
if (schema != null && p.equals(RDF.TYPE)) {
final Resource objClass = (Resource) o;
if (schema.hasRestriction(objClass)) {
sb.append(" { ");
sb.append(schema.explainRestriction(objClass));
sb.append(" }");
}
}
sb.append(sep);
}
if (isInference()) {
sb.append(derivation.explain(multiline, prefix, schema));
}
else {
sb.append("[input]");
}
return sb.toString();
}
/**
* Represent the content only, not the derivation.
*/
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
if (triple != null) {
sb.append("<").append(getSubject().toString()).append("> ");
sb.append("<").append(getPredicate().toString()).append("> ");
if (getObject() instanceof Literal) {
sb.append(getObject().toString());
}
else {
sb.append("<").append(getObject().toString()).append(">");
}
}
return sb.append(" .").toString();
}
@Override
public void write(final DataOutput out) throws IOException {
if (triple == null) {
out.writeInt(0);
}
else {
final StringBuilder sb = new StringBuilder();
if (triple.getContext() != null) {
sb.append(triple.getContext().toString());
}
sb.append(SEP).append(getSubject().toString());
sb.append(SEP).append(getPredicate().toString());
sb.append(SEP).append(getObject().toString());
final byte[] encoded = sb.toString().getBytes(StandardCharsets.UTF_8);
out.writeInt(encoded.length);
out.write(encoded);
}
out.writeBoolean(useful);
// Write the derivation if there is one
final boolean derived = isInference();
out.writeBoolean(derived);
if (derived) {
derivation.write(out);
}
}
@Override
public void readFields(final DataInput in) throws IOException {
derivation = null;
final int tripleLength = in.readInt();
if (tripleLength == 0) {
triple = null;
}
else {
final byte[] tripleBytes = new byte[tripleLength];
in.readFully(tripleBytes);
final String tripleString = new String(tripleBytes, StandardCharsets.UTF_8);
final String[] parts = tripleString.split(SEP);
final ValueFactory factory = SimpleValueFactory.getInstance();
final String context = parts[0];
Resource s = null;
final IRI p = factory.createIRI(parts[2]);
Value o = null;
// Subject: either bnode or URI
if (parts[1].startsWith("_")) {
s = factory.createBNode(parts[1].substring(2));
}
else {
s = factory.createIRI(parts[1]);
}
// Object: literal, bnode, or URI
if (parts[3].startsWith("_")) {
o = factory.createBNode(parts[3].substring(2));
}
else if (parts[3].startsWith("\"")) {
//literal: may have language or datatype
final int close = parts[3].lastIndexOf("\"");
final int length = parts[3].length();
final String label = parts[3].substring(1, close);
if (close == length - 1) {
// Just a string enclosed in quotes
o = factory.createLiteral(label);
}
else {
final String data = parts[3].substring(close + 1);
if (data.startsWith(LiteralLanguageUtils.LANGUAGE_DELIMITER)) {
final String lang = data.substring(1);
o = factory.createLiteral(label, lang);
}
else if (data.startsWith("^^<")) {
o = factory.createLiteral(label, factory.createIRI(
data.substring(3, data.length() - 1)));
}
}
}
else {
o = factory.createIRI(parts[3]);
}
// Create a statement with or without context
if (context.isEmpty()) {
triple = VF.createStatement(s, p, o);
}
else {
triple = VF.createStatement(s, p, o, factory.createIRI(context));
}
}
useful = in.readBoolean();
if (in.readBoolean()) {
derivation = new Derivation();
derivation.readFields(in);
}
}
/**
* Defines an ordering based on equals.
* Two ReasonerFacts belong together if they represent the same
* triple (regardless of where it came from). If they both are empty
* (represent no triple), compare their derivations instead.
*/
@Override
public int compareTo(final Fact other) {
if (this.equals(other)) {
return 0;
}
else if (other == null) {
return 1;
}
if (this.triple == null) {
if (other.triple == null) {
// The only case where Derivation matters
return this.getDerivation().compareTo(other.getDerivation());
}
else {
// triple > no triple
return -1;
}
}
else if (other.triple == null) {
// triple > no triple
return 1;
}
// Compare two triples, ignoring where the information came from
int result = this.getSubject().toString().compareTo(
other.getSubject().toString());
if (result == 0) {
result = this.getPredicate().toString().compareTo(
other.getPredicate().toString());
if (result == 0) {
result = this.getObject().toString().compareTo(
other.getObject().toString());
}
}
return result;
}
/**
* Two ReasonerFacts are equivalent if they represent the same triple
* (regardless of where it came from). If they don't contain triples,
* compare their derivations.
*/
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || this.getClass() != o.getClass()) {
return false;
}
final Fact other = (Fact) o;
if (this.triple == null) {
if (other.triple == null) {
// Derivations only matter if both facts are empty
return this.getDerivation().equals(other.getDerivation());
}
else {
return false;
}
}
else {
return this.triple.equals(other.triple);
}
}
/**
* Two statements are the same as long as they represent the same triple.
* Derivation matters if and only if there is no triple.
*/
@Override
public int hashCode() {
if (triple == null) {
return getDerivation().hashCode();
}
else {
return triple.hashCode();
}
}
@Override
public Fact clone() {
final Fact other = new Fact();
other.triple = this.triple;
other.useful = this.useful;
if (this.derivation != null) {
other.derivation = this.derivation.clone();
}
return other;
}
/**
* Specify a source. Wrapper for Derivation.addSource. Instantiates a
* derivation if none exists.
*/
public void addSource(final Fact other) {
if (derivation == null) {
derivation = new Derivation();
}
derivation.addSource(other);
}
/**
* If this is a derived fact, get the iteration it was derived, otherwise
* return zero.
*/
public int getIteration() {
if (derivation == null) {
return 0;
}
else {
return derivation.getIteration();
}
}
/**
* Return whether this fact has itself as a source.
*/
public boolean isCycle() {
return derivation != null && derivation.hasSource(this);
}
/**
* Return whether a particular fact is identical to one used to derive this.
* Wrapper for Derivation.hasSource.
*/
public boolean hasSource(final Fact other) {
return derivation != null && derivation.hasSource(other);
}
/**
* Return whether this fact was derived using a particular rule.
*/
public boolean hasRule(final OwlRule rule) {
return derivation != null && derivation.getRule() == rule;
}
/**
* Get the size of the derivation tree, computed by counting up the number
* of distinct nodes that are part of this edge or were used to produce this
* fact, minus 1. An input triple has span 1, a triple derived in one reduce
* step has span 2, etc. Related to the derivation's span, but takes
* subject and object of this fact into account.
*/
public int span() {
if (isInference()) {
int d = derivation.span() + 1;
if (derivation.hasSourceNode(getSubject())) {
d--;
}
if (derivation.hasSourceNode(getObject())) {
d--;
}
return d;
}
else {
return 1;
}
}
}