/**
* PRISSMA is a presentation-level framework for Linked Data adaptation.
*
* Copyright (C) 2013 Luca Costabello, v1.0
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see <http://www.gnu.org/licenses/>.
*/
package fr.inria.wimmics.prissma.selection;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.ResIterator;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.ResourceFactory;
import com.hp.hpl.jena.rdf.model.SimpleSelector;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import fr.inria.wimmics.prissma.selection.entities.ContextUnit;
import fr.inria.wimmics.prissma.selection.entities.DecompItem;
import fr.inria.wimmics.prissma.selection.entities.Decomposition;
import fr.inria.wimmics.prissma.selection.entities.Edge;
import fr.inria.wimmics.prissma.selection.entities.Prism;
import fr.inria.wimmics.prissma.selection.exceptions.CuttingEdgeException;
import fr.inria.wimmics.prissma.selection.utilities.ContextUnitConverter;
public class Decomposer {
private Logger LOG = LoggerFactory.getLogger(Decomposer.class);
public Decomposer() {
super();
}
/**
* Wrapper method and entry point for decomposition procedure.
* 1) Trim fresnel data from prism
* 2) Substitute intermediate entities w/ their class
* 3) Apply recursive decomp method
*
* @param model
* @param decomp
* @return
*/
public Decomposition decompose(Model prismModel, Decomposition decomp){
Prism prism = getPrismObject(prismModel);
prism.rootNode = ContextUnitConverter.switchToClasses(prism.rootNode, decomp);
decomp = decomposeMain(prism.rootNode, prism.prismURI, decomp);
return decomp;
}
/**
* Detects prism URI and filter out its direct properties.
* @param prismModel
* @return
*/
public Prism getPrismObject(Model prismModel) {
Prism prism = new Prism();
List<Statement> ignoreSt = new ArrayList<Statement>();
try {
prism.prismURI = null;
Resource prismRes = null;
StmtIterator it = prismModel.listStatements(
new SimpleSelector((Resource)null, PrissmaProperties.pPurpose, (RDFNode) null) );
while (it.hasNext()){
Statement st = it.next();
prismRes = st.getSubject();
prism.prismURI = new URI(prismRes.getURI());
}
if (prismRes != null){
// remove direct prissma:Prism properties (e.g. prissma:purpose)
it = prismModel.listStatements(new SimpleSelector(prismRes, (Property) null, (RDFNode) null) );
while (it.hasNext()){
ignoreSt.add(it.next());
}
// detect triples w/ prissma:Prism as object and get the subjects (Lenses and Formats)
// removes Lenses and Formats triples for each lens and format
Resource list = null;
it = prismModel.listStatements(new SimpleSelector((Resource) null, (Property) null, prismRes) );
while (it.hasNext()){
Statement st = it.next();
Resource lensOrFormat = st.getSubject();
StmtIterator it2 = prismModel.listStatements(new SimpleSelector(lensOrFormat, (Property) null, (RDFNode) null));
while (it2.hasNext()){
Statement st2 = it2.next();
// is it is a bag (fresnel showproperties/ hideproperties)
if (st2.getPredicate().equals(PrissmaProperties.pShowProperties) ||
st2.getPredicate().equals(PrissmaProperties.pHideProperties)){
list = st2.getObject().asResource();
StmtIterator it3 = prismModel.listStatements(new SimpleSelector(list, (Property) null, (RDFNode) null));
while (it3.hasNext()){
Statement st3 = it3.next();
ignoreSt.add(st3);
}
}
ignoreSt.add(st2);
}
}
}
StmtIterator it3 = prismModel.listStatements(new SimpleSelector((Resource) null, PrissmaProperties.pSpurious1, (RDFNode) null));
while (it3.hasNext()){
Statement st3 = it3.next();
ignoreSt.add(st3);
}
it3 = prismModel.listStatements(new SimpleSelector((Resource) null, PrissmaProperties.pSpurious2, (RDFNode) null));
while (it3.hasNext()){
Statement st3 = it3.next();
ignoreSt.add(st3);
}
prismModel.remove(ignoreSt);
prism.rootNode = ContextUnitConverter.getRootCtxNode(prismModel);
if (prism.prismURI == null){
LOG.error("Prism URI not found. ");
}
return prism;
} catch (URISyntaxException e) {
LOG.error("Error retrieving Prism URI: " + e.getMessage());
return prism;
}
}
public URI getPrismURI(Model prismModel) {
try {
Resource prismRes = null;
StmtIterator it = prismModel.listStatements(
new SimpleSelector((Resource)null, PrissmaProperties.pPurpose, (RDFNode) null) );
while (it.hasNext()){
Statement st = it.next();
prismRes = st.getSubject();
}
return new URI(prismRes.getURI());
} catch (URISyntaxException e) {
LOG.error("Error retrieving URI of Prism: " + e.getMessage());
return null;
}
}
/**
* The decomposition main method
* @param prismURL
* @return
*/
private Decomposition decomposeMain(RDFNode g, URI prismURI, Decomposition decomp){
RDFNode sMax = null,
gMinusSmax = null;
Edge e = null;
// if g is ctxunit, it cannot be decomposed any further.
if (!ContextUnitConverter.isCtxUnit(g)){
sMax = searchSmax(decomp, g);
// if g is already in the decomposition, exit.
if (ContextUnitConverter.areIsomorphic(g, sMax)){
if (prismURI != null && !prismURI.equals("")){
int id = decomp.getItem(g);
decomp.elements.get(id).prismURISet.add(prismURI);
}
return decomp;
}
// choose and recursively decompose sMax
if (sMax == null){
try {
e = chooseCuttingEdge(g, decomp.substitutions.values());
sMax = createSMax(e, g);
gMinusSmax = difference(g, sMax, e);
} catch (CuttingEdgeException e1) {
LOG.error("Error choosing cutting edge in decomposition. " + e1.getMessage() );
return null;
}
decomp = decomposeMain(sMax, null, decomp);
decomp = decomposeMain(gMinusSmax, null, decomp);
// add sMax to decomposition
updateDecomp(decomp, g, sMax, gMinusSmax, e, prismURI);
}
else{
RDFNode ancestor1 = sMax;
List<Edge> edges = getConnectingEdges(g, sMax, decomp.substitutions.values());
Iterator<Edge> it = edges.iterator();
while (it.hasNext()){
URI currURI = null;
Edge eConn = it.next();
RDFNode pathNode = getPath(g, eConn, edges);
gMinusSmax = difference(pathNode, sMax, eConn);
decomp = decomposeMain(gMinusSmax, null, decomp);
// add gMinusSmax decomp element to decomposition, but put prismURI only if it the last one (the real prism)
if (!it.hasNext() || edges.size() == 1)
currURI = prismURI;
DecompItem lastAddedEl = updateDecomp(decomp, g, ancestor1, gMinusSmax, eConn, currURI);
ancestor1 = decomp.getReconstructedModel(lastAddedEl);
}
}
} else
// add ctxunit to decomp
updateDecomp(decomp, g, sMax, gMinusSmax, e, prismURI);
return decomp;
}
private RDFNode getPath(RDFNode g, Edge edge, List<Edge> connEdges) {
// init
Model mG = g.getModel();
if (mG == null)
return null;
if (mG.isEmpty())
return g;
Model mCopy = ModelFactory.createDefaultModel();
mCopy.add(mG.listStatements());
List<Resource> objects = new ArrayList<Resource>();
// clean below
StmtIterator it = mCopy.listStatements(edge.v1.instance.asResource(),
(Property) null, (RDFNode) null);
while (it.hasNext()) {
Statement st = it.next();
Property pred = st.getPredicate();
RDFNode obj = st.getObject();
if (!(pred.equals(edge.label) && obj.equals(edge.v2.instance))){
if (obj.isResource())
objects.add(obj.asResource());
it.remove();
}
}
for (Resource obj : objects)
removeStatements(mCopy, obj, null);
// clean outside of smax
for (Edge otherConnEdge : connEdges) {
if (!otherConnEdge.equals(edge) && !otherConnEdge.v1.instance.equals(edge.v1.instance)){
removeStatements(mCopy, otherConnEdge.v1.instance,otherConnEdge);
mCopy.remove(otherConnEdge.v1.instance.asResource(),
otherConnEdge.label,
otherConnEdge.v2.instance);
}
}
// return cleaned path
return ContextUnitConverter.getRoot(mCopy, true);
}
private DecompItem updateDecomp(Decomposition decomp, RDFNode g, RDFNode sMax,
RDFNode gMinusSmax, Edge e, URI prismURI) {
DecompItem gDecompItem = new DecompItem(decomp, g, sMax, gMinusSmax, e, prismURI);
if (!decomp.elements.contains(gDecompItem)){
decomp.elements.add(decomp.idCounter, gDecompItem);
decomp.idCounter ++;
}
return gDecompItem;
}
/**
* Searches for the edge that connects sMax w/ the rest of the input graph.
* @param g
* @param sMax
* @return
*/
private List<Edge> getConnectingEdges(RDFNode g, RDFNode sMax, Collection<String> RDFclasses) {
List<Edge> connectingEdges = new ArrayList<Edge>();
Model mSMax = sMax.getModel();
Model mG = g.getModel();
Statement sConn = null;
List<Statement> sConnList = new ArrayList<Statement>();
// if smax is ctxunit
if (mSMax == null || mSMax.isEmpty()) {
// check for subject connection ( do it only if sMax can be subject, i.e. it's a resource)
if (sMax.isResource()) {
StmtIterator it = mG.listStatements(sMax.asResource(),
(Property) null, (RDFNode) null);
while (it.hasNext()) {
Statement st = it.next();
sConn = ResourceFactory.createStatement(
st.getSubject(), st.getPredicate(),
st.getObject());
sConnList.add(sConn);
}
}
// now scan for object
StmtIterator it = mG.listStatements((Resource) null,
(Property) null, sMax);
while (it.hasNext()) {
Statement st = it.next();
sConn = ResourceFactory.createStatement(st.getSubject(),
st.getPredicate(), st.getObject());
sConnList.add(sConn);
}
}
// if smax is NOT ctxunit
else {
// check each node in Smax for outgoing/incoming connection
StmtIterator itSMax = mSMax.listStatements();
while (itSMax.hasNext()) {
Statement s = itSMax.next();
Resource sub = s.getSubject();
RDFNode obj = s.getObject();
// check subject, first for outgoing...
StmtIterator itG = mG.listStatements(sub, (Property) null,(RDFNode) null);
while (itG.hasNext()) {
sConn = itG.next();
// check if it is a different property because g contains sMAx, therefore also all its triples
if (!mSMax.contains(sConn) && !sConnList.contains(sConn))
sConnList.add(sConn);
}
// ... then for incoming connections
StmtIterator itOut = mG.listStatements((Resource)null, (Property) null,sub);
while (itOut.hasNext()) {
sConn = itOut.next();
// check if it a different property because g contains sMAx, therefore also all its triples
if (!mSMax.contains(sConn) && !sConnList.contains(sConn))
sConnList.add(sConn);
}
// check object for outgoing
if (obj.isResource()) {
Resource objRes = obj.asResource();
itG = mG.listStatements(objRes, (Property) null,(RDFNode) null);
while (itG.hasNext()) {
sConn = itG.next();
// check if it a different property because g contains sMAx, therefore also all its triples
if (!mSMax.contains(sConn) && !sConnList.contains(sConn))
sConnList.add(sConn);
}
}
}
}
// wrap into Edge instances
for (Statement sConnFound : sConnList) {
Edge e = new Edge();
e.label = sConnFound.getPredicate();
ContextUnitConverter c = new ContextUnitConverter();
c.convertInputToUnits(sConnFound.getSubject(), RDFclasses);
for (ContextUnit cu : c.inputGraphContextUnits){
if (cu.instance.equals(sConnFound.getSubject()))
e.v1 = cu;
}
c = new ContextUnitConverter();
c.convertInputToUnits(sConnFound.getObject(), RDFclasses);
for (ContextUnit cu : c.inputGraphContextUnits){
if (cu.instance.equals(sConnFound.getObject()))
e.v2 = cu;
}
connectingEdges.add(e);
}
return connectingEdges;
}
/**
* Search decomposition for largest element sMax that is shared w/ input.
* Uses jena triple-based subgraph procedure.
* @param decomp
* @param g
* @return
*/
private RDFNode searchSmax(Decomposition decomp, RDFNode g) {
RDFNode sMax = null;
for ( DecompItem element : decomp.elements) {
// size of decomp element is the # of context units that are contained in it.
RDFNode elementNode = decomp.getReconstructedModel(element);
if ( isSubgraphOfTripleBased(elementNode, g) &&
(sMax == null ||
getSize(sMax, decomp.substitutions.values()) < getSize(elementNode, decomp.substitutions.values()))
){
sMax = decomp.getReconstructedModel(element);
}
}
return sMax;
}
/**
* Returns the number of ctxUnit contained in a graph.
* N.b: size != number of nodes (GEO ctx unit and TIME count as one even if they are
* made of multiple nodes)
* @param node
* @return
*/
private long getSize(RDFNode node, Collection<String> RDFclasses) {
Model model = node.getModel();
if (model != null){
ContextUnitConverter converter = new ContextUnitConverter();
converter.convertInputToUnits(node, RDFclasses);
return converter.inputGraphContextUnits.size();
}
else
// is ctxUnit
return 1;
}
private RDFNode difference(RDFNode g, RDFNode sMax, Edge e) {
RDFNode gMinusSmax = null;
Model gM = g.getModel();
Model sMaxM = sMax.getModel();
Statement cutStmt = ResourceFactory.createStatement(e.v1.instance.asResource(), e.label, e.v2.instance);
// if sMax ctxUnit
if (sMaxM == null || sMaxM.isEmpty()){
Model mCopy = ModelFactory.createDefaultModel();
mCopy.add(gM.listStatements());
mCopy.remove(cutStmt);
// if gminusSmax is supposed to be a ctxunit
if (mCopy.isEmpty()){
if (e.v2.instance.isResource())
gMinusSmax = ResourceFactory.createResource(e.v2.instance.asResource().getURI());
else if (e.v2.instance.isLiteral())
gMinusSmax = ResourceFactory.createPlainLiteral(e.v2.instance.asLiteral().getString());
return gMinusSmax;
}
// if smax is subject of Edge e
if (e.v1.instance.equals(sMax)){
if (e.v2.instance.isResource())
gMinusSmax = ContextUnitConverter.getRoot(mCopy, true);
else if (e.v2.instance.isLiteral())
gMinusSmax = ContextUnitConverter.getRoot(mCopy, false);
}
// Smax is object of Edge e
// i.e. e.v2.instance.equals(sMax) == true
else {
gMinusSmax = ContextUnitConverter.getRoot(mCopy, true);
}
return gMinusSmax;
}
// if smax not ctxunit
else {
Model gMinusSmaxM = gM.difference(sMaxM);
// if gMinusSmax has only one triple, this triple is the cut triple.
// Must check if this cut triple has siblings, and if so, must return the object of the statement.
if (gMinusSmaxM.size() == 1){
ResIterator it = gMinusSmaxM.listSubjects();
while (it.hasNext()){
Resource subj = it.next();
if (subj.equals(cutStmt.getSubject()))
gMinusSmax = cutStmt.getObject();
}
} else {
gMinusSmaxM.remove(cutStmt);
gMinusSmax = ContextUnitConverter.getRoot(gMinusSmaxM, true);
}
}
return gMinusSmax;
}
/**
* Creates the RDFNode object that will consist in the first half of the decomposition process.
* @param e
* @param g
* @return
*/
private RDFNode createSMax(Edge e, RDFNode g) {
Model gModel = g.getModel();
// create a copy of model to not interfere
Model mCopy = ModelFactory.createDefaultModel();
mCopy.add(gModel.listStatements());
// get cutprop form model to get complete branch
// (cut property always present, so cutStmt never null)
StmtIterator it = gModel.listStatements(e.v1.instance.asResource(), e.label, e.v2.instance);
Statement cutStmt = null;
if (it.hasNext())
cutStmt = it.next();
// remove statements outside the area of graph delimited by cutProperty
removeStatements(mCopy, cutStmt.getObject(), null);
RDFNode sMaxRDFNode = ContextUnitConverter.getRoot(mCopy, true);
if (ContextUnitConverter.isCtxUnit(sMaxRDFNode))
return ContextUnitConverter.getRoot(mCopy, false);
else{
sMaxRDFNode.getModel().remove(cutStmt);
return sMaxRDFNode;
}
}
private void removeStatements(Model m, RDFNode node, Edge exception) {
if (!node.isResource())
return;
Resource res = node.asResource();
if (getPropertiesCount(res) == 0)
return;
Statement exceptionSt = null;
if (exception!=null)
exceptionSt = ResourceFactory.createStatement(exception.v1.instance.asResource(),
exception.label,
exception.v2.instance);
StmtIterator it = res.listProperties();
while(it.hasNext()){
Statement s = it.next();
if (exceptionSt == null || !exceptionSt.equals(s))
removeStatements(m, s.getObject(),exception);
}
m.remove(res.listProperties());
return;
}
private int getPropertiesCount(Resource res) {
int count = 0;
StmtIterator it = res.listProperties();
while(it.hasNext()){
it.next();
count++;
}
return count;
}
/**
* Choose the edge between smax and the complementary graph.
* The edge would be added to the decomposition element.
* Priority given to core prissma properties.
* @param g
* @return
* @throws CuttingEdgeException
*/
private Edge chooseCuttingEdge(RDFNode gnode, Collection<String> RDFclasses) throws CuttingEdgeException {
Model g = gnode.getModel();
StmtIterator iter = g.listStatements();
Edge e;
// Convert input g into ctx units.
// Ctx unit conversion is needed to associate ctxUnit nodes to each Edge.
ContextUnitConverter converter = new ContextUnitConverter();
converter.convertInputToUnits(gnode, RDFclasses);
// search for prissma core properties first. Pick one.
for (Property coreProperty : PrissmaProperties.priorityCutProperties) {
if (g.contains( (Resource)null, coreProperty, (RDFNode)null)){
e = converter.getEdge(coreProperty);
return e;
}
}
// if no prissma core property found, get the first returned property.
// Exclude internal properties of ctx units and PRISSMA core properties
while(iter.hasNext()){
Statement stmt = iter.nextStatement();
Property currentProperty = stmt.getPredicate();
if (!Arrays.asList(PrissmaProperties.internalCtxUnitProperties).contains(currentProperty) &&
!isPriorityCutProp(currentProperty) ){
e = converter.getEdge(currentProperty);
return e;
}
}
// This means there were only filter properties in g.
// Should never happen, because ctx unit are never dissected.
throw new CuttingEdgeException();
}
private boolean isPriorityCutProp(Property currentProperty) {
List<Property> propList = Arrays.asList(PrissmaProperties.priorityCutProperties);
if (propList.contains(currentProperty))
return true;
else
return false;
}
private boolean isSubgraphOfTripleBased(RDFNode gElement, RDFNode g) {
boolean result = false;
Model gElementModel = gElement.getModel();
// if decomp element is ctxUnit
if (gElementModel == null || gElementModel.isEmpty()){
result = g.getModel().containsResource(gElement);
return result;
}
// if decompelement not ctxunit
if (g.getModel().containsAll(gElement.getModel()))
result = true;
return result;
}
}