com.hp.hpl.jena.n3.N3JenaWriterPP.java Source code

Java tutorial

Introduction

Here is the source code for com.hp.hpl.jena.n3.N3JenaWriterPP.java

Source

/*
 * 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.
 */

package com.hp.hpl.jena.n3;

//import org.apache.commons.logging.*;
import com.hp.hpl.jena.rdf.model.*;
import com.hp.hpl.jena.shared.JenaException;
import com.hp.hpl.jena.vocabulary.RDF;
import com.hp.hpl.jena.vocabulary.RDFS;
import com.hp.hpl.jena.util.iterator.*;

import java.util.*;

/** An N3 pretty printer.
 *  Tries to make N3 data look readable - works better on regular data.
 */

public class N3JenaWriterPP extends N3JenaWriterCommon
/*implements RDFWriter*/
{
    // This N3 writer proceeds in 2 stages.  First, it analysises the model to be
    // written to extract information that is going to be specially formatted
    // (RDF lists, small anon nodes) and to calculate the prefixes that will be used.

    final protected boolean doObjectListsAsLists = getBooleanValue("objectLists", true);

    // Data structures used in controlling the formatting

    protected Set<Resource> rdfLists = null; // Heads of lists
    protected Set<Resource> rdfListsAll = null; // Any resources in a collection
    protected Set<Resource> rdfListsDone = null; // RDF lists written
    protected Set<RDFNode> oneRefObjects = null; // Bnodes referred to once as an object - can inline
    protected Set<Resource> oneRefDone = null; // Things done - so we can check for missed items

    // Do we do nested (one reference) nodes?
    protected boolean allowDeep = true;

    static final protected String objectListSep = " , ";

    // ----------------------------------------------------
    // Prepatation stage

    @Override
    protected void prepare(Model model) {
        prepareLists(model);
        prepareOneRefBNodes(model);
    }

    // Find well-formed RDF lists - does not find empty lists (this is intentional)
    // Works by finding all tails, and work backwards to the head.
    // RDF lists may, or may not, have a type element.
    // Should do this during preparation, not as objects found during the write
    // phase.   

    protected void prepareLists(Model model) {
        Set<Resource> thisListAll = new HashSet<>();

        StmtIterator listTailsIter = model.listStatements(null, RDF.rest, RDF.nil);

        // For every tail of a list
        //tailLoop:
        for (; listTailsIter.hasNext();) {
            // The resource for the current element being considered.
            Resource listElement = listTailsIter.nextStatement().getSubject();
            // The resource pointing to the link we have just looked at.
            Resource validListHead = null;

            // Chase to head of list
            for (;;) {
                boolean isOK = checkListElement(listElement);
                if (!isOK)
                    break;

                // At this point the element is exactly a collection element.
                if (N3JenaWriter.DEBUG)
                    out.println("# RDF list all: " + formatResource(listElement));
                validListHead = listElement;
                thisListAll.add(listElement);

                // Find the previous node.
                StmtIterator sPrev = model.listStatements(null, RDF.rest, listElement);

                if (!sPrev.hasNext())
                    // No rdf:rest link
                    break;

                // Valid pretty-able list.  Might be longer.
                listElement = sPrev.nextStatement().getSubject();
                if (sPrev.hasNext()) {
                    if (N3JenaWriter.DEBUG)
                        out.println("# RDF shared tail from " + formatResource(listElement));
                    break;
                }
            }
            // At head of a pretty-able list - add its elements and its head.
            if (N3JenaWriter.DEBUG)
                out.println("# Collection list head: " + formatResource(validListHead));
            rdfListsAll.addAll(thisListAll);
            if (validListHead != null)
                rdfLists.add(validListHead);
        }
        listTailsIter.close();
    }

    // Validate one list element.
    protected boolean checkListElement(Resource listElement) {
        // Must be a blank node for abbreviated form.
        if (!listElement.isAnon())
            return false;

        if (!listElement.hasProperty(RDF.rest) || !listElement.hasProperty(RDF.first)) {
            if (N3JenaWriter.DEBUG)
                out.println("# RDF list element does not have required properties: " + formatResource(listElement));
            return false;
        }

        // Must be exactly two properties (the ones we just tested for)
        // or three including the RDF.type RDF.List statement.
        int numProp = countProperties(listElement);

        if (numProp == 2)
            // Must have exactly the properties we just tested for.
            return true;

        // rdf:type is not implicit.
        //        if (numProp == 3)
        //        {
        //            if (listElement.hasProperty(RDF.type, RDF.List))
        //                return true;
        //            if (N3JenaWriter.DEBUG)
        //                out.println(
        //                    "# RDF list element: 3 properties but no rdf:type rdf:List"
        //                        + formatResource(listElement));
        //            return false;
        //        }

        if (N3JenaWriter.DEBUG)
            out.println("# RDF list element does not right number of properties: " + formatResource(listElement));
        return false;
    }

    // Find bnodes that are objects of only one statement (and hence can be inlined)
    // which are not RDF lists.
    // Could do this testing at write time (unlike lists)

    protected void prepareOneRefBNodes(Model model) {

        NodeIterator objIter = model.listObjects();
        for (; objIter.hasNext();) {
            RDFNode n = objIter.nextNode();

            if (testOneRefBNode(n))
                oneRefObjects.add(n);
            objIter.close();

            // N3JenaWriter.DEBUG
            if (N3JenaWriter.DEBUG) {
                out.println("# RDF Lists      = " + rdfLists.size());
                out.println("# RDF ListsAll   = " + rdfListsAll.size());
                out.println("# oneRefObjects  = " + oneRefObjects.size());
            }
        }
    }

    protected boolean testOneRefBNode(RDFNode n) {
        if (!(n instanceof Resource))
            return false;

        Resource obj = (Resource) n;

        if (!obj.isAnon())
            return false;

        // In a list - done as list, not as embedded bNode.
        if (rdfListsAll.contains(obj))
            // RDF list (head or element)
            return false;

        StmtIterator pointsToIter = obj.getModel().listStatements(null, null, obj);
        if (!pointsToIter.hasNext())
            // Corrupt graph!
            throw new JenaException("N3: found object with no arcs!");

        Statement s = pointsToIter.nextStatement();

        if (pointsToIter.hasNext())
            return false;

        if (N3JenaWriter.DEBUG)
            out.println("# OneRef: " + formatResource(obj));
        return true;
    }

    // ----------------------------------------------------
    // Output stage 

    // Property order is:
    // 1 - rdf:type (as "a")
    // 2 - other rdf: rdfs: namespace items (sorted)
    // 3 - all other properties, sorted by URI (not qname)  

    @Override
    protected ClosableIterator<Property> preparePropertiesForSubject(Resource r) {
        Set<Property> seen = new HashSet<>();
        boolean hasTypes = false;
        SortedMap<String, Property> tmp1 = new TreeMap<>();
        SortedMap<String, Property> tmp2 = new TreeMap<>();

        StmtIterator sIter = r.listProperties();
        for (; sIter.hasNext();) {
            Property p = sIter.nextStatement().getPredicate();
            if (seen.contains(p))
                continue;
            seen.add(p);

            if (p.equals(RDF.type)) {
                hasTypes = true;
                continue;
            }

            if (p.getURI().startsWith(RDF.getURI()) || p.getURI().startsWith(RDFS.getURI())) {
                tmp1.put(p.getURI(), p);
                continue;
            }

            tmp2.put(p.getURI(), p);
        }
        sIter.close();

        ExtendedIterator<Property> eIter = null;

        if (hasTypes)
            eIter = new SingletonIterator<>(RDF.type);

        ExtendedIterator<Property> eIter2 = WrappedIterator.create(tmp1.values().iterator());

        eIter = (eIter == null) ? eIter2 : eIter.andThen(eIter2);

        eIter2 = WrappedIterator.create(tmp2.values().iterator());

        eIter = (eIter == null) ? eIter2 : eIter.andThen(eIter2);
        return eIter;
    }

    @Override
    protected boolean skipThisSubject(Resource subj) {
        return rdfListsAll.contains(subj) || oneRefObjects.contains(subj);
    }

    //    protected void writeModel(Model model)
    //   {
    //        super.writeModel(model) ;
    //
    //

    // Before ... 

    @Override
    protected void startWriting() {
        allocateDatastructures();
    }

    // Flush any unwritten objects.
    // 1 - OneRef objects
    //     Normally there are "one ref" objects left
    //     However loops of "one ref" are possible.
    // 2 - Lists

    @Override
    protected void finishWriting() {

        // Are there any unattached RDF lists?
        // e..g ([] [] []) . in the data.
        // We missed these earlier.
        for (Resource r : rdfLists) {
            if (rdfListsDone.contains(r)) {
                continue;
            }
            out.println();
            if (N3JenaWriter.DEBUG) {
                out.println("# RDF List");
            }

            // Includes the case of shared lists-as-objects. 
            //            if (!r.isAnon() || countArcsTo(r) > 0 )
            //            {
            //                // Name it.
            //                out.print(formatResource(r));
            //                out.print(" :- ");
            //            }
            //            writeList(r);
            //            out.println(" .");
            // Turtle does not have :-
            writeListUnpretty(r);
        }

        // Finally, panic.
        // Dump anything that has not been output yet. 
        oneRefObjects.removeAll(oneRefDone);
        for (RDFNode oneRefObject : oneRefObjects) {
            out.println();
            if (N3JenaWriter.DEBUG) {
                out.println("# One ref");
            }
            // Don't allow further one ref objects to be inlined.
            allowDeep = false;
            writeOneGraphNode((Resource) oneRefObject);
            allowDeep = true;
        }

        //out.println() ;
        //writeModelSimple(model,  bNodesMap, base) ;
        out.flush();
        clearDatastructures();
    }

    // Need to decide between one line or many.
    // Very hard to do a pretty thing here because the objects may be large or small or a mix.

    @Override
    protected void writeObjectList(Resource subject, Property property) {
        //        if ( ! doObjectListsAsLists )
        //        {
        //            super.writeObjectList(resource, property) ;
        //            return ;
        //        }

        String propStr = formatProperty(property);

        // Find which objects are simple (i.e. not nested structures)             

        StmtIterator sIter = subject.listProperties(property);
        Set<RDFNode> simple = new HashSet<>();
        Set<RDFNode> complex = new HashSet<>();

        for (; sIter.hasNext();) {
            Statement stmt = sIter.nextStatement();
            RDFNode obj = stmt.getObject();
            if (isSimpleObject(obj))
                simple.add(obj);
            else
                complex.add(obj);
        }
        sIter.close();
        // DEBUG
        int simpleSize = simple.size();
        int complexSize = complex.size();

        // Write property/simple objects

        if (simple.size() > 0) {
            String padSp = null;
            // Simple objects - allow property to be long and alignment to be lost
            if ((propStr.length() + minGap) <= widePropertyLen)
                padSp = pad(calcPropertyPadding(propStr));

            if (doObjectListsAsLists) {
                // Write all simple objects as one list. 
                out.print(propStr);
                out.incIndent(indentObject);

                if (padSp != null)
                    out.print(padSp);
                else
                    out.println();

                for (Iterator<RDFNode> iter = simple.iterator(); iter.hasNext();) {
                    RDFNode n = iter.next();
                    writeObject(n);

                    // As an object list
                    if (iter.hasNext())
                        out.print(objectListSep);
                }

                out.decIndent(indentObject);
            } else {
                for (Iterator<RDFNode> iter = simple.iterator(); iter.hasNext();) {
                    // This is also the same as the complex case 
                    // except the width the property can go in is different.
                    out.print(propStr);
                    out.incIndent(indentObject);
                    if (padSp != null)
                        out.print(padSp);
                    else
                        out.println();

                    RDFNode n = iter.next();
                    writeObject(n);
                    out.decIndent(indentObject);

                    // As an object list
                    if (iter.hasNext())
                        out.println(" ;");
                }

            }
        }
        // Now do complex objects.
        // Write property each time for a complex object.
        // Do not allow over long properties but same line objects.

        if (complex.size() > 0) {
            // Finish the simple list if there was one
            if (simple.size() > 0)
                out.println(" ;");

            int padding = -1;
            String padSp = null;

            // Can we fit teh start of teh complex object on this line?

            // DEBUG variable.
            int tmp = propStr.length();
            // Complex objects - do not allow property to be long and alignment to be lost
            if ((propStr.length() + minGap) <= propertyCol) {
                padding = calcPropertyPadding(propStr);
                padSp = pad(padding);
            }

            for (Iterator<RDFNode> iter = complex.iterator(); iter.hasNext();) {
                int thisIndent = indentObject;
                //if ( i )
                out.incIndent(thisIndent);
                out.print(propStr);
                if (padSp != null)
                    out.print(padSp);
                else
                    out.println();

                RDFNode n = iter.next();
                writeObject(n);
                out.decIndent(thisIndent);
                if (iter.hasNext())
                    out.println(" ;");
            }
        }
        return;
    }

    protected boolean isSimpleObject(RDFNode node) {
        if (node instanceof Literal)
            return true;
        Resource rObj = (Resource) node;
        if (allowDeep && oneRefObjects.contains(rObj))
            return false;
        return true;
    }

    @Override
    protected void writeObject(RDFNode node) {
        if (node instanceof Literal) {
            writeLiteral((Literal) node);
            return;
        }

        Resource rObj = (Resource) node;
        if (allowDeep && !isSimpleObject(rObj)) {
            oneRefDone.add(rObj);
            ClosableIterator<Property> iter = preparePropertiesForSubject(rObj);
            if (!iter.hasNext()) {
                // No properties.
                out.print("[]");
            } else {
                out.print("[ ");
                out.incIndent(2);
                writePropertiesForSubject(rObj, iter);
                out.decIndent(2);
                out.println();
                // Line up []
                out.print("]");
            }
            iter.close();
            return;
        }

        if (rdfLists.contains(rObj))
            if (countArcsTo(rObj) <= 1) {
                writeList(rObj);
                return;
            }

        out.print(formatResource(rObj));
    }

    // Need to out.print in short (all on one line) and long forms (multiple lines)
    protected void writeList(Resource resource) {
        out.print("(");
        out.incIndent(2);
        boolean listFirst = true;
        for (Iterator<RDFNode> iter = rdfListIterator(resource); iter.hasNext();) {
            if (!listFirst)
                out.print(" ");
            listFirst = false;
            RDFNode n = iter.next();
            writeObject(n);
        }
        out.print(")");
        out.decIndent(2);
        rdfListsDone.add(resource);

    }

    // Need to out.print in long (triples) form.
    protected void writeListUnpretty(Resource r) {
        //for ( ; ! r.equals(RDF.nil); )
        {
            // Write statements at this node.
            StmtIterator sIter = r.getModel().listStatements(r, null, (RDFNode) null);
            for (; sIter.hasNext();) {
                Statement s = sIter.next();
                writeStatement(s);
            }

            // Look for rdf:rest.
            sIter = r.getModel().listStatements(r, RDF.rest, (RDFNode) null);
            for (; sIter.hasNext();) {
                Statement s = sIter.next();
                RDFNode nextNode = s.getObject();
                if (nextNode instanceof Resource) {
                    Resource r2 = (Resource) nextNode;
                    writeListUnpretty(r2);
                } else
                    writeStatement(s);
            }
        }
    }

    private void writeStatement(Statement s) {
        out.print(formatResource(s.getSubject()));
        out.print(" ");

        out.print(formatResource(s.getPredicate()));
        out.print(" ");

        out.print(formatNode(s.getObject()));
        out.println(" .");

    }

    // Called before each writing run.
    protected void allocateDatastructures() {
        rdfLists = new HashSet<>();
        rdfListsAll = new HashSet<>();
        rdfListsDone = new HashSet<>();
        oneRefObjects = new HashSet<>();
        oneRefDone = new HashSet<>();
    }

    // Especially release large intermediate memory objects
    protected void clearDatastructures() {
        rdfLists = null;
        rdfListsAll = null;
        rdfListsDone = null;
        oneRefObjects = null;
        oneRefDone = null;
    }
}