org.mindswap.swoop.renderer.entity.NLVisitor.java Source code

Java tutorial

Introduction

Here is the source code for org.mindswap.swoop.renderer.entity.NLVisitor.java

Source

//The MIT License
//
// Copyright (c) 2004 Mindswap Research Group, University of Maryland, College Park
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

package org.mindswap.swoop.renderer.entity;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import org.apache.commons.lang.StringEscapeUtils;
import org.mindswap.swoop.SwoopModel;
import org.mindswap.swoop.renderer.BaseEntityRenderer;
import org.mindswap.swoop.renderer.SwoopRenderingVisitor;
import org.semanticweb.owl.io.ShortFormProvider;
import org.semanticweb.owl.model.OWLAnd;
import org.semanticweb.owl.model.OWLClass;
import org.semanticweb.owl.model.OWLDataAllRestriction;
import org.semanticweb.owl.model.OWLDataCardinalityRestriction;
import org.semanticweb.owl.model.OWLDataEnumeration;
import org.semanticweb.owl.model.OWLDataProperty;
import org.semanticweb.owl.model.OWLDataPropertyInstance;
import org.semanticweb.owl.model.OWLDataPropertyRangeAxiom;
import org.semanticweb.owl.model.OWLDataSomeRestriction;
import org.semanticweb.owl.model.OWLDataType;
import org.semanticweb.owl.model.OWLDataValue;
import org.semanticweb.owl.model.OWLDataValueRestriction;
import org.semanticweb.owl.model.OWLDescription;
import org.semanticweb.owl.model.OWLDifferentIndividualsAxiom;
import org.semanticweb.owl.model.OWLDisjointClassesAxiom;
import org.semanticweb.owl.model.OWLEnumeration;
import org.semanticweb.owl.model.OWLEquivalentClassesAxiom;
import org.semanticweb.owl.model.OWLEquivalentPropertiesAxiom;
import org.semanticweb.owl.model.OWLException;
import org.semanticweb.owl.model.OWLFunctionalPropertyAxiom;
import org.semanticweb.owl.model.OWLIndividual;
import org.semanticweb.owl.model.OWLIndividualTypeAssertion;
import org.semanticweb.owl.model.OWLInverseFunctionalPropertyAxiom;
import org.semanticweb.owl.model.OWLInversePropertyAxiom;
import org.semanticweb.owl.model.OWLNot;
import org.semanticweb.owl.model.OWLObjectAllRestriction;
import org.semanticweb.owl.model.OWLObjectCardinalityRestriction;
import org.semanticweb.owl.model.OWLObjectProperty;
import org.semanticweb.owl.model.OWLObjectPropertyInstance;
import org.semanticweb.owl.model.OWLObjectPropertyRangeAxiom;
import org.semanticweb.owl.model.OWLObjectSomeRestriction;
import org.semanticweb.owl.model.OWLObjectValueRestriction;
import org.semanticweb.owl.model.OWLOr;
import org.semanticweb.owl.model.OWLProperty;
import org.semanticweb.owl.model.OWLPropertyDomainAxiom;
import org.semanticweb.owl.model.OWLSameIndividualsAxiom;
import org.semanticweb.owl.model.OWLSubClassAxiom;
import org.semanticweb.owl.model.OWLSubPropertyAxiom;
import org.semanticweb.owl.model.OWLSymmetricPropertyAxiom;
import org.semanticweb.owl.model.OWLTransitivePropertyAxiom;
import org.semanticweb.owl.model.helper.OWLObjectVisitorAdapter;

import qtag.Tagger;

public class NLVisitor extends OWLObjectVisitorAdapter implements SwoopRenderingVisitor {

    ShortFormProvider shortForms;
    StringWriter sw;
    PrintWriter pw;
    SwoopModel swoopModel;

    static Tagger tagger;

    //**** Define elements to create Natural Language Tree
    NLTree tree;
    NLNode parent;
    NLLink linkContext;
    HashMap nodeStore;
    Hashtable hyperlinkMap;
    OWLProperty linkProp;
    static String LINK_EQUIVALENT = "is"; // NEW CHANGE
    static String LINK_SUBCLASS = "is";
    static String LINK_COMPLEMENT = "is not a";
    static String LINK_INTERSECTION = "is";
    static String LINK_UNION = "or a";
    static String LINK_ALLVALUES = "that always";
    static String LINK_SOMEVALUES = "that";
    static String LINK_HASVALUE = "that";
    static String LINK_MAXCARD = "with at most";
    static String LINK_MINCARD = "with at least";
    static String LINK_CARD = "with exactly";
    static String LINK_ONEOF = "either";
    static String LINK_BETWEEN = "with between";
    //*****

    public NLVisitor(ShortFormProvider shortForms, SwoopModel swoopModel) {
        try {
            tagger = new Tagger("lib/qtag-eng");
        } catch (IOException e) {
            System.out.println("Error: POS library not found!");
        }
        this.shortForms = shortForms;
        this.swoopModel = swoopModel;
        hyperlinkMap = new Hashtable();
        reset();
    }

    /**
     * HTML-escape an object
     * @param o
     * @return
     */
    private static String escape(Object o) {
        return StringEscapeUtils.escapeHtml(o.toString());
    }

    public String result() {
        return sw.toString();
    }

    public void reset() {
        sw = new StringWriter();
        pw = new PrintWriter(sw);
    }

    public void resetNLTree(String entityName, String link, int type) {
        // set entity name as the root node of the tree
        NLNode root = new NLNode(entityName, type);
        // create a new NL tree with this root
        this.tree = new NLTree(root);
        // set current parent in NL-Visitor as root
        this.parent = root;
        // set current link (use argument passed as linktype)
        this.linkContext = new NLLink("", link);
        // used to reset parents during each iteration of a and/or/oneof
        nodeStore = new HashMap();
    }

    public void resetParent() {
        this.parent = tree.getRoot();
    }

    public void setLinkContext(String linkType) {
        linkContext.setLinkType(linkType);
        linkContext.setKeyword("");
    }

    public void visit(OWLClass clazz) throws OWLException {
        // build NL tree 
        String className = getShortForm(clazz.getURI());
        String tokens = getEntityTokens(className);
        // add hyperlink
        hyperlinkMap.put(tokens, "<a href=\"" + clazz.getURI() + "\">" + tokens + "</a>");
        NLNode target = new NLNode(tokens, 0);

        parent.addLink(linkContext, target);
    }

    public void visit(OWLIndividual ind) throws OWLException {
        // build NL tree 
        String indName = getShortForm(ind.getURI());
        String tokens = getEntityTokens(indName);
        // add hyperlink
        hyperlinkMap.put(tokens, "<a href=\"" + ind.getURI() + "\">" + tokens + "</a>");
        NLNode target = new NLNode(tokens, 3);

        parent.addLink(linkContext, target);
    }

    // TODO this is just object properties
    public void visit(OWLObjectProperty prop) throws OWLException {
        // build NL tree 
        String propName = getShortForm(prop.getURI());
        String tokens = getEntityTokens(propName).toLowerCase();
        String[] tok = getEntityTokens(propName).toLowerCase().split("( )+");
        if (!tokens.startsWith("has") && !tokens.startsWith("is")) {

            // tagger.setInput(tokens);
            String[] tags = tagger.tag(tok);
            System.out.print(tokens + " TAG:");

            for (int i = 0; i < tags.length; i++) {
                System.out.print(tags[i].toString() + " ");
            }
            System.out.println();

            if (tok.length == 1) {
                //              TODO: there is a problem if the word is ambiguous between a noun and a verb
                // for example: drives. a possible solution is to get the complete tagging and see if
                // such ambiguity exists, and always prefer the verb form, since plural nouns in properties 
                // are apparently rare

                // To solve this problems, strips 's' from word and checks if the resulting form can be a verb
                if (tags[0].equals("NNS")) {
                    String possibleVerb = tok[0].substring(0, tok[0].length() - 2);
                    String[] testTags = tagger.tag(tok);
                    if (testTags[0].startsWith("V")) {
                        tok[0] = possibleVerb;
                        tags = tagger.tag(tok);
                        //System.out.print(tokens + " NEW TAG:"  );
                    }
                }

                if (tags[0].startsWith("VB")) {
                    if (tags[0].equals("VBN")) {
                        tokens = "is " + tokens; // FOR TESTING NOW
                    } else if (tags[0].startsWith("VBD")) {
                        tokens = "is " + tokens;
                    }
                } else if (tags[0].startsWith("N") && !tags[0].endsWith("S")) { // only singular nouns
                    tokens = "has " + tokens;
                } else {
                    //tokens = "has " + tokens;
                    // unidentified prop type?
                }
            } else {
                if (tags[0].startsWith("NN")) {
                    String possibleVerb = tok[0].substring(0, tok[0].length() - 2);
                    String[] testTags = tagger.tag(tok);
                    if (testTags[0].startsWith("V")) {
                        tok[0] = possibleVerb;
                        tags = tagger.tag(tok);
                    }
                }

                int propClass = classifyComplexProp(tags, tok);

                System.err.println(propClass);

                //              0 = complex np (just multiple nouns)         phone number
                // 1 = np and p                         child of
                // 2 = vp and np                        produces wine
                // 3 = vp and p                         located in
                // 4 = vp and pp (p and np)                  made from grape
                // -1 = other (unrecognized)               prop12
                switch (propClass) {
                case 0:
                    tokens = "has " + tokens;
                    break;
                case 1:
                    tokens = "is a " + tokens;
                    break;
                case 2:
                case 3:
                case 4:
                    if (tags[0].equals("VBN")) {
                        tokens = "is " + tokens; // FOR TESTING NOW
                    } else if (tags[0].startsWith("VBD")) {
                        tokens = "is " + tokens;
                    }
                    break;
                case 5:
                    // unfortunately can't get here
                    String newTokens = new String(tok[0] + "a ");
                    for (int i = 1; i < tok.length; i++) {
                        newTokens = newTokens.concat(tok[i] + " ");
                    }

                    tokens = newTokens;

                    break;
                }
            }

        } else if (tokens.startsWith("is") && tok.length > 1) {
            String[] tags = tagger.tag(tok);
            System.out.print(tokens + " TAG:");

            for (int i = 0; i < tags.length; i++) {
                System.out.print(tags[i].toString() + " ");
            }
            System.out.println();

            int propClass = classifyComplexProp(tags, tok);

            if (propClass == 5) {
                String newTokens = new String(tok[0] + " a ");
                for (int i = 1; i < tok.length; i++) {
                    newTokens = newTokens.concat(tok[i] + " ");
                }

                tokens = newTokens;
            }
        }

        // add hyperlink
        hyperlinkMap.put(tokens, "<a href=\"" + prop.getURI() + "\">" + tokens + "</a>");

        linkContext.setKeyword(tokens);
        linkProp = prop;
    }

    // TODO dataproperty is not the same as 
    public void visit(OWLDataProperty prop) throws OWLException {
        // build NL tree 
        String propName = getShortForm(prop.getURI());
        //
        String tokens = getEntityTokens(propName).toLowerCase();
        String[] tok = getEntityTokens(propName).toLowerCase().split("( )+");
        if (!tokens.startsWith("has") && !tokens.startsWith("is")) {

            String[] tags = tagger.tag(tok);
            // System.out.println(tokens+" TAG:"+tags);   
            if (tags[0].startsWith("VB")) {
                tokens = "is " + tokens; // heuristic for verbs
            } else {
                tokens = "has " + tokens; // heuristic for nouns
            }
        }

        // add hyperlink
        hyperlinkMap.put(tokens, "<a href=\"" + prop.getURI() + "\">" + tokens + "</a>");

        linkContext.setKeyword(tokens);
        linkProp = prop;
    }

    public void visit(OWLDataValue cd) throws OWLException {

        String dVal = " \"" + escape(cd.getValue()) + "\"";
        NLNode target = new NLNode(getEntityTokens(dVal), 3);
        parent.addLink(linkContext, target);
    }

    public void visit(OWLAnd and) throws OWLException {

        if (linkProp != null)
            prefixDomain(linkProp); // suppose object of prop restr is a intersection

        String saveCode = String.valueOf(this.parent.hashCode());
        nodeStore.put(saveCode, parent);

        boolean restoreLC = linkContext.isComplement(); // restore link complement for each intersection element

        for (Iterator it = and.getOperands().iterator(); it.hasNext();) {
            linkContext.setLinkType(LINK_INTERSECTION);
            linkContext.setKeyword("");
            linkContext.setIsComplement(restoreLC);
            OWLDescription desc = (OWLDescription) it.next();
            desc.accept(this);
            this.parent = (NLNode) nodeStore.get(saveCode); // reset parent at each iteration

            // TESTING:
            //         this.printTree();
            //         System.out.println();
        }
    }

    public void visit(OWLOr or) throws OWLException {

        if (linkProp != null)
            prefixDomain(linkProp); // suppose object of prop restr is a union
        String saveCode = String.valueOf(this.parent.hashCode());
        nodeStore.put(saveCode, parent);

        boolean restoreLC = linkContext.isComplement(); // restore link complement for each union element

        for (Iterator it = or.getOperands().iterator(); it.hasNext();) {
            linkContext.setLinkType(LINK_UNION);
            linkContext.setKeyword("");
            linkContext.setIsComplement(restoreLC);
            OWLDescription desc = (OWLDescription) it.next();
            desc.accept(this);
            this.parent = (NLNode) nodeStore.get(saveCode); // reset parent at each iteration
        }
    }

    public void visit(OWLNot not) throws OWLException {

        if (linkProp != null)
            prefixDomain(linkProp); // suppose object of prop restr is a complement
        linkContext.setIsComplement(true); // used to print "not" when object is a description
        linkContext.setLinkType(NLVisitor.LINK_COMPLEMENT); // used to print "not" when object is a class
        linkContext.setKeyword("");
        OWLDescription desc = not.getOperand();
        desc.accept(this);
    }

    public void visit(OWLEnumeration enumeration) throws OWLException {

        System.out.println("Visiting Enumeration");

        String saveCode = String.valueOf(this.parent.hashCode());
        nodeStore.put(saveCode, parent);

        boolean restoreLC = linkContext.isComplement(); // restore link complement for each oneof element

        //NLLink originalLinkContext = linkContext;

        String enumString = new String();

        for (Iterator it = enumeration.getIndividuals().iterator(); it.hasNext();) {
            //linkContext.setLinkType(LINK_ONEOF);
            //linkContext.setIsComplement(restoreLC);
            OWLIndividual desc = (OWLIndividual) it.next();
            //desc.accept( this );
            //this.parent = (NLNode) nodeStore.get(saveCode); // reset parent at each iteration

            String indName = getShortForm(desc.getURI());
            String tokens = getEntityTokens(indName);
            enumString = enumString.concat(indName + ";;;");
        }

        NLNode enum_ = new NLNode(enumString, 4); // create dummy node ?
        parent.addLink(linkContext, enum_);
    }

    public void visit(OWLObjectSomeRestriction restriction) throws OWLException {
        prefixDomain(restriction.getObjectProperty());
        linkContext.setLinkType(LINK_SOMEVALUES);
        restriction.getObjectProperty().accept(this);
        restriction.getDescription().accept(this);
    }

    public void visit(OWLObjectAllRestriction restriction) throws OWLException {
        prefixDomain(restriction.getObjectProperty());
        linkContext.setLinkType(LINK_ALLVALUES);
        restriction.getObjectProperty().accept(this);
        restriction.getDescription().accept(this);
    }

    public void visit(OWLObjectValueRestriction restriction) throws OWLException {
        prefixDomain(restriction.getObjectProperty());
        linkContext.setLinkType(LINK_HASVALUE);
        restriction.getObjectProperty().accept(this);
        restriction.getIndividual().accept(this);
    }

    public void visit(OWLDataSomeRestriction restriction) throws OWLException {
        prefixDomain(restriction.getDataProperty());
        linkContext.setLinkType(LINK_SOMEVALUES);
        restriction.getDataProperty().accept(this);
        restriction.getDataType().accept(this);
    }

    public void visit(OWLDataAllRestriction restriction) throws OWLException {
        prefixDomain(restriction.getDataProperty());
        linkContext.setLinkType(LINK_ALLVALUES);
        restriction.getDataProperty().accept(this);
        restriction.getDataType().accept(this);
    }

    public void visit(OWLObjectCardinalityRestriction restriction) throws OWLException {

        prefixDomain(restriction.getObjectProperty());
        int ncard = -1;
        if (restriction.isExactly()) {
            linkContext.setLinkType(LINK_CARD);
            ncard = restriction.getAtLeast();
        } else if (restriction.isAtMost()) {
            linkContext.setLinkType(LINK_MAXCARD);
            ncard = restriction.getAtMost();
        } else if (restriction.isAtLeast()) {
            linkContext.setLinkType(LINK_MINCARD);
            ncard = restriction.getAtLeast();
        }
        restriction.getObjectProperty().accept(this);

        NLNode cardNode = new NLNode(String.valueOf(ncard), 2);
        parent.addLink(linkContext, cardNode);
    }

    public void visit(OWLDataCardinalityRestriction restriction) throws OWLException {

        prefixDomain(restriction.getProperty());
        int ncard = -1;
        if (restriction.isExactly()) {
            linkContext.setLinkType(LINK_CARD);
            ncard = restriction.getAtLeast();
        } else if (restriction.isAtMost()) {
            linkContext.setLinkType(LINK_MAXCARD);
            ncard = restriction.getAtMost();
        } else if (restriction.isAtLeast()) {
            linkContext.setLinkType(LINK_MINCARD);
            ncard = restriction.getAtLeast();
        }
        restriction.getDataProperty().accept(this);

        NLNode cardNode = new NLNode(String.valueOf(ncard), 2);
        parent.addLink(linkContext, cardNode);
    }

    public void visit(OWLDataValueRestriction restriction) throws OWLException {
        prefixDomain(restriction.getDataProperty());
        linkContext.setLinkType(LINK_HASVALUE);
        restriction.getDataProperty().accept(this);
        restriction.getValue().accept(this);
    }

    public void visit(OWLEquivalentClassesAxiom axiom) throws OWLException {
        pw.print("EquivalentClasses(");
        for (Iterator it = axiom.getEquivalentClasses().iterator(); it.hasNext();) {
            OWLDescription desc = (OWLDescription) it.next();
            desc.accept(this);
            if (it.hasNext()) {
                pw.print(" ");
            }
        }
        pw.print(")");
    }

    public void visit(OWLDisjointClassesAxiom axiom) throws OWLException {
        pw.print("(");
        for (Iterator it = axiom.getDisjointClasses().iterator(); it.hasNext();) {
            OWLDescription desc = (OWLDescription) it.next();
            desc.accept(this);
            if (it.hasNext()) {
                pw.print(" " + ConciseFormat.DISJOINT + " ");
            }
        }
        pw.print(")");
    }

    public void visit(OWLSubClassAxiom axiom) throws OWLException {
        pw.print("(");
        axiom.getSubClass().accept(this);
        pw.print(" " + ConciseFormat.SUBSET + " ");
        axiom.getSuperClass().accept(this);
        pw.print(")");
    }

    public void visit(OWLEquivalentPropertiesAxiom axiom) throws OWLException {
        pw.print("(");
        for (Iterator it = axiom.getProperties().iterator(); it.hasNext();) {
            OWLProperty prop = (OWLProperty) it.next();
            prop.accept(this);
            if (it.hasNext()) {
                pw.print(" = ");
            }
        }
        pw.print(")");
    }

    public void visit(OWLSubPropertyAxiom axiom) throws OWLException {
        pw.print("(");
        axiom.getSubProperty().accept(this);
        pw.print(" " + ConciseFormat.SUBSET + " ");
        axiom.getSuperProperty().accept(this);
        pw.print(")");
    }

    public void visit(OWLDifferentIndividualsAxiom ax) throws OWLException {
        pw.print("(");
        for (Iterator it = ax.getIndividuals().iterator(); it.hasNext();) {
            OWLIndividual desc = (OWLIndividual) it.next();
            desc.accept(this);
            if (it.hasNext()) {
                pw.print(" " + ConciseFormat.DISJOINT + " ");
            }
        }
        pw.print(")");
    }

    public void visit(OWLSameIndividualsAxiom ax) throws OWLException {
        pw.print("SameIndividual(");
        for (Iterator it = ax.getIndividuals().iterator(); it.hasNext();) {
            OWLIndividual desc = (OWLIndividual) it.next();
            desc.accept(this);
            if (it.hasNext()) {
                pw.print(" = ");
            }
        }
        pw.print(")");
    }

    public void visit(OWLDataType ocdt) throws OWLException {
        //pw.print( "<a href=\"" + ocdt.getURI() + "\">" + getShortForm( ocdt.getURI() ) + "</a>" );      
        //pw.print( getShortForm( ocdt.getURI() ) );
    }

    public void visit(OWLDataEnumeration enumeration) throws OWLException {
        pw.print("{");
        for (Iterator it = enumeration.getValues().iterator(); it.hasNext();) {
            linkContext.setLinkType(LINK_ONEOF);
            OWLDataValue desc = (OWLDataValue) it.next();
            desc.accept(this);
            if (it.hasNext()) {
                pw.print(", ");
            }
        }
        pw.print("}");
    }

    public void prefixDomain(OWLProperty prop) {

        System.out.print("PROP: " + prop);

        String domainName = "";
        URI uri = null;
        try {
            for (Iterator it = swoopModel.getReasoner().domainsOf(prop).iterator(); it.hasNext();) {
                OWLDescription dom = (OWLDescription) it.next();
                if (dom instanceof OWLClass) {
                    uri = ((OWLClass) dom).getURI();
                    domainName = getShortForm(((OWLClass) dom).getURI());
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (domainName.equals(""))
                domainName = "Thing"; // THIS WAS CHANGED FROM thing

            String tokens = getEntityTokens(domainName);
            // add hyperlink
            hyperlinkMap.put(tokens, "<a href=\"" + uri + "\">" + tokens + "</a>");
            NLNode target = new NLNode(tokens, 0);

            //NLLink isaLink = new NLLink("", this.LINK_SUBCLASS);
            parent = parent.addLink(linkContext, target);
        }

        System.out.println(" DOM: " + domainName);
    }

    // this is what does the splitting of prop names into tokens, here is where we add 
    // delimiter support in the future
    public String getEntityTokens(String entityName) {

        String[] tokens = new String[10];
        String strTokens = "";
        int tLen = 0;
        int prev = 0;

        for (int i = 1; i < entityName.length(); i++) {
            if (Character.isUpperCase(entityName.charAt(i))) {
                //if (entityName.charAt(i) >= 'A' && entityName.charAt(i) <= 'Z') {
                if (i - prev > 0) {
                    strTokens += (entityName.substring(prev, i) + " ");
                    tokens[tLen++] = entityName.substring(prev, i);
                    prev = i;
                }
            }
            if (i == (entityName.length() - 1)) {
                // System.out.println(i+":"+prev+":"+entityName+":"+entityName.substring(prev));
                tokens[tLen] = entityName.substring(prev);
                strTokens += entityName.substring(prev);
            }
        }

        return strTokens;

    }

    public String getShortForm(URI uri) throws OWLException {
        String sf = shortForms.shortForm(uri);
        if (sf.indexOf(":") >= 0)
            sf = sf.substring(sf.indexOf(":") + 1, sf.length());
        return sf;
    }

    public void printTree() {

        NLNode root = tree.getRoot();
        cleanUpThingNodes();
        printNode(root, true, 0);
    }

    private void putLinks(List sorted, HashMap map) {
        for (Iterator i = map.keySet().iterator(); i.hasNext();) {
            NLLink link = (NLLink) i.next();
            NLNode node = (NLNode) map.get(link);
            sorted.add(new NLLinkNode(link, node));
        }
    }

    // onlyPrefix == false means return the rest, not the whole thing
    private String getPart(String sentence, boolean onlyPrefix) {

        // remove hyperlink stuff if any
        if (sentence.indexOf("<a href") >= 0) {
            sentence = sentence.substring(sentence.indexOf("\">") + 2, sentence.indexOf("</a>"));
        }

        if (sentence.indexOf(" ") == -1)
            return sentence;
        if (onlyPrefix) {
            String prefix = sentence.substring(0, sentence.indexOf(" "));
            return prefix;
        } else {
            String rest = sentence.substring(sentence.indexOf(" ") + 1, sentence.length());
            return rest;
        }
    }

    /* New NLP Functions - Daniel */

    // 0 = complex np (just multiple nouns)         phone number
    // 1 = np and p                         child of
    // 2 = vp and np                        produces wine
    // 3 = vp and p                         located in
    // 4 = vp and pp (p and np)                  made from grape
    // 5 = is NP of                           is specific function of
    // -1 = other (unrecognized)               prop12

    private int classifyComplexProp(String[] tags, String[] tokens) {
        boolean containsVerb = false;
        boolean containsPrep = false;
        boolean containsNoun = false;
        boolean containsOf = false;
        boolean containsIs = false;

        for (int i = 0; i < tags.length; i++) {
            String curr = tags[i];

            System.err.println(tokens[i] + " " + tags[i]);

            if (curr.startsWith("V")) {
                containsVerb = true;
            } else if (curr.startsWith("N")) {
                containsNoun = true;

                if (i == tags.length - 1) {
                    if (containsVerb && containsPrep) {
                        return 4;
                    } else if (containsVerb && !containsPrep) {
                        return 2;
                    }
                }
            } else if (curr.startsWith("IN")) {
                containsPrep = true;

                System.out.println("PREP: " + tokens[i]);

                if (tokens[i].equals("of"))
                    containsOf = true;

                if (i == tags.length - 1) {
                    if (containsOf) { // I assume that anything with of will always have an NP
                        if (containsIs) { // is brother of
                            System.out.println("RETURNING 5");
                            return 5;
                        } else { // brother of
                            return 1;
                        }
                    }

                    if (containsVerb) {
                        return 3;
                    } else if (containsNoun) {
                        return 1;
                    }

                    //                   if ( containsNoun && !containsVerb ) {
                    //                       return 1;
                    //                   } else if ( containsVerb && !containsNoun ) {
                    //                       return 3;
                    //                   } 
                }
            } else if (curr.startsWith("B")) {
                containsIs = true;
            }
        }

        if (containsOf && containsIs)
            return 5;

        if (containsNoun && !containsVerb && !containsPrep) {
            return 0;
        }

        return -1;
    }

    private void cleanUpThingNodes() {
        NLNode root = tree.getRoot();

        System.out.println("BEGINNING THING REMOVAL");

        printDebugTree();

        reconcileThingNodes(root);

        printDebugTree();

        //reconcileNegativeThingNodes( root );

        //printDebugTree();

        reconcileNonIsaThingNodes(root);

        printDebugTree();

        System.out.println("END THING REMOVAL");
    }

    public void printDebugTree() {
        printDebugNode(tree.getRoot(), 0);
    }

    private void printDebugNode(NLNode node, int indent) {
        System.out.println(node.keyword);

        HashMap links = node.getLinks();
        Set keys = links.keySet();

        for (Iterator iter = keys.iterator(); iter.hasNext();) {
            // cycle through each link
            NLLink link = (NLLink) iter.next();
            NLNode target = (NLNode) links.get(link);

            for (int i = 0; i < indent; i++)
                System.out.print("\t");

            System.out.print("(" + link.linkType + ") " + link.keyword + " ");

            printDebugNode(target, indent + 1);
        }
    }

    // TODO make this fix the problem of multiple all values on same property
    private void mergeAllValues(NLNode node) {

    }

    // PROPERTIES
    private void reconcileNonIsaThingNodes(NLNode node) {
        HashMap links = node.getLinks();
        Set keys = links.keySet();
        NLLink isaLink = new NLLink("", NLVisitor.LINK_SUBCLASS);

        boolean thingFound = false;
        Set isaLinks = new HashSet();
        //Set nonIsaLinks = new HashSet();
        Set nonIsaThingLinks = new HashSet(); // may be more than one, unlike is-a
        for (Iterator iter = new HashSet(keys).iterator(); iter.hasNext();) {
            // cycle through each link
            NLLink link = (NLLink) iter.next();
            NLNode target = (NLNode) links.get(link);

            System.out.println("LINK: " + link.keyword + " (" + link.linkType + "), TARGET: " + target.keyword);

            if (!link.equals(isaLink)) {
                //nonIsaLinks.add( link );

                if (target.getKeyword().equals("Thing") || target.getKeyword().equals("thing")) {
                    System.out.println("NON-ISA thing found");
                    nonIsaThingLinks.add(link);
                }
            }
        }

        // pulling-up named classes for non-isa thing links
        for (Iterator nt = nonIsaThingLinks.iterator(); nt.hasNext();) {
            NLLink curr = (NLLink) nt.next();

            NLNode thingNode = (NLNode) links.get(curr);
            HashMap tLinks = thingNode.getLinks();

            Set tNonThingTargets = new HashSet();
            Set tKeys = tLinks.keySet();
            for (Iterator i = tKeys.iterator(); i.hasNext();) {
                NLLink tLink = (NLLink) i.next();
                NLNode tTarget = (NLNode) tLinks.get(tLink);

                if (tLink.equals(isaLink)) {
                    if (!tTarget.getKeyword().equals("Thing") && !tTarget.getKeyword().equals("thing")) {
                        System.out.println("pull-up");

                        System.out.println(links);

                        links.remove(curr);

                        links.put(curr, tTarget);

                        tTarget.links.putAll(thingNode.links);
                        tTarget.links.remove(tLink);
                    }
                }
            }
        }

        // Recurse
        keys = links.keySet();
        for (Iterator iter = keys.iterator(); iter.hasNext();) {
            NLLink link = (NLLink) iter.next();
            NLNode target = (NLNode) links.get(link);

            System.out.println("NEW LINK: " + link.keyword + " (" + link.linkType + "), TARGET: " + target.keyword);

            reconcileNonIsaThingNodes(target);
        }
    }

    // recursive function to squash Thing nodes - COMPLEMENT
    private void reconcileNegativeThingNodes(NLNode node) {
        HashMap links = node.getLinks();
        NLLink isNotALink = new NLLink("", NLVisitor.LINK_COMPLEMENT);

        // first: gather all the is-a links
        NLLink nonThingLink = null;
        NLLink thingLink = null;
        boolean thingFound = false;
        Set isaLinks = new HashSet();
        for (Iterator iter = links.keySet().iterator(); iter.hasNext();) {
            NLLink link = (NLLink) iter.next();
            NLNode target = (NLNode) links.get(link);

            //System.out.println( "LINK: " + link.keyword + " (" + link.linkType + "), TARGET: " + target.keyword );

            if (link.equals(isNotALink)) {
                isaLinks.add(link);

                if (target.getKeyword().equals("Thing") || target.getKeyword().equals("thing")) {
                    //System.out.println( "thing found" );
                    thingFound = true;
                    thingLink = link;
                } else {
                    nonThingLink = link;
                }
            }
        }

        //System.out.println( "NEG-ISA's: " + isaLinks.size() + " " + thingFound);

        if (thingFound) {
            if (isaLinks.size() == 0) {
                // this should never happen 
            } else if (isaLinks.size() == 1) {
                // there is only one node and it is a thing node, so we can safely pull its links up
                NLNode thingNode = (NLNode) links.get(thingLink);

                HashMap tLinks = thingNode.getLinks();
                node.links.putAll(tLinks);
                links.remove(thingLink);
            } else {
                NLNode thingNode = (NLNode) links.get(thingLink);
                NLNode nonThingNode = (NLNode) links.get(nonThingLink);

                HashMap tLinks = thingNode.getLinks();
                HashMap ntLinks = nonThingNode.getLinks();
                ntLinks.putAll(tLinks);
                links.remove(thingLink);
            }
        }

        // Recurse
        for (Iterator iter = links.keySet().iterator(); iter.hasNext();) {
            NLLink link = (NLLink) iter.next();
            NLNode target = (NLNode) links.get(link);

            //System.out.println( "NEW LINK: " + link.keyword + " (" + link.linkType + "), TARGET: " + target.keyword );

            reconcileNegativeThingNodes(target);
        }
    }

    // recursive function to squash Thing nodes - ISA
    private void reconcileThingNodes(NLNode node) {
        HashMap links = node.getLinks();
        Set keys = links.keySet();
        NLLink isaLink = new NLLink("", NLVisitor.LINK_SUBCLASS);

        // first: gather all the is-a links
        NLLink nonThingLink = null;
        NLLink thingLink = null;
        boolean thingFound = false;
        Set isaLinks = new HashSet();
        for (Iterator iter = new HashSet(keys).iterator(); iter.hasNext();) {
            // cycle through each link
            NLLink link = (NLLink) iter.next();
            NLNode target = (NLNode) links.get(link);

            System.out.println("LINK: " + link.keyword + " (" + link.linkType + "), TARGET: " + target.keyword);

            if (link.equals(isaLink)) {
                isaLinks.add(link);

                if (target.getKeyword().equals("Thing") || target.getKeyword().equals("thing")) {
                    System.out.println("thing found");
                    thingFound = true;
                    thingLink = link;
                } else {
                    nonThingLink = link;
                }
            }
        }

        System.out.println("ISA's: " + isaLinks.size() + " " + thingFound);

        if (thingFound) {
            if (isaLinks.size() == 0) {
                // this should never happen 
            } else if (isaLinks.size() == 1) {
                // there is only one node and it is a thing node, so we can safely pull its links up
                NLNode thingNode = (NLNode) links.get(thingLink);

                HashMap tLinks = thingNode.getLinks();
                node.links.putAll(tLinks);
                links.remove(thingLink);
            } else {
                NLNode thingNode = (NLNode) links.get(thingLink);
                NLNode nonThingNode = (NLNode) links.get(nonThingLink);

                HashMap tLinks = thingNode.getLinks();
                HashMap ntLinks = nonThingNode.getLinks();
                ntLinks.putAll(tLinks);
                links.remove(thingLink);
            }
        }

        // Recurse
        keys = links.keySet();
        for (Iterator iter = keys.iterator(); iter.hasNext();) {
            NLLink link = (NLLink) iter.next();
            NLNode target = (NLNode) links.get(link);

            System.out.println("NEW LINK: " + link.keyword + " (" + link.linkType + "), TARGET: " + target.keyword);

            reconcileThingNodes(target);
        }
    }

    private boolean containsNP(String propName) {
        if (propName.startsWith("has") || propName.startsWith("is"))
            return true;

        String[] tokens = propName.split("( )+");
        String[] tags = tagger.tag(tokens);
        if (tokens.length > 1) {
            int result = classifyComplexProp(tags, tokens);

            if (result == 2 || result == 4)
                return true;
        }

        return false;
    }

    private void processAllValues(NLNode node, int indent) {

        HashMap links = node.getLinks();
        boolean allPrinted = false;

        NLLink isaLink = new NLLink("", NLVisitor.LINK_SUBCLASS);
        for (Iterator iter = links.keySet().iterator(); iter.hasNext();) {
            // cycle through each link
            NLLink link = (NLLink) iter.next();
            NLNode target = (NLNode) links.get(link);

            System.out.println("LINK: " + link.keyword + " (" + link.linkType + "), TARGET: " + target.keyword);

            if (link.equals(isaLink)) {
                for (Iterator ti = target.getLinks().keySet().iterator(); ti.hasNext();) {
                    NLLink tLink = (NLLink) ti.next();
                    NLNode tTarget = (NLNode) target.getLinks().get(tLink);

                    if (tLink.getLinkType().equals(NLVisitor.LINK_ALLVALUES)) {
                        System.out.println("All Values present");

                        boolean simpleAll = !containsNP(tLink.getKeyword());

                        if (simpleAll) {
                            pw.print("If a " + node.getKeyword() + " " + tLink.getKeyword() + " something");
                            pw.println(", then that thing:");
                            pw.print("\t- ");
                        } else {
                            pw.print("If a " + node.getKeyword() + " " + getPart(tLink.getKeyword(), true) + " a "
                                    + getPart(tLink.getKeyword(), false));
                            pw.println(", then that " + getPart(tLink.getKeyword(), false) + ":");
                            pw.print("\t- ");
                        }

                        // old part
                        pw.print(link.getLinkType() + " ");

                        // new part
                        printNode(tTarget, false, indent + 1);

                        // the all-values is already printed, so remove it
                        ti.remove();
                        allPrinted = true;
                    }

                    if (allPrinted)
                        pw.println();
                }
            } else if (link.getLinkType().equals(NLVisitor.LINK_ALLVALUES)) {
                System.out.println("All Values present");

                boolean simpleAll = !containsNP(link.getKeyword());

                if (simpleAll) {
                    pw.print("If a " + node.getKeyword() + " " + link.getKeyword() + " something");
                    pw.println(", then that thing:");
                    pw.print("\t- ");
                } else {
                    pw.print("If a " + node.getKeyword() + " " + getPart(link.getKeyword(), true) + " a "
                            + getPart(link.getKeyword(), false));
                    pw.println(", then that " + getPart(link.getKeyword(), false) + ":");
                    pw.print("\t- ");
                }

                // old part sans linkType - because we know its all values
                pw.print("is ");

                // new part
                printNode(target, false, indent + 1);

                // the all-values is already printed, so remove it
                iter.remove();
                allPrinted = true;

                pw.println();
            }
        }
    }

    /**
     * Recursive algorithm to print a single NL tree node
     * @param node - print a subtree of this node
     * @param basic TODO
     */
    public void printNode(NLNode node, boolean topLevel, int indent) {
        System.out.println("printing node: " + node.keyword + " " + node.links + " type:" + node.nodeType);

        // processes and REMOVES all all-values nodes
        processAllValues(node, indent);

        if (node.nodeType == 0) {
            pw.print("a ");
        } else if (node.nodeType == 4) {
            String[] elements = node.getKeyword().split(";;;");
            if (elements.length == 0) {
                System.out.println("There is a prolem: Empty Enumeration");
            } else if (elements.length == 1) {
                pw.print(elements[0] + " ");
            } else if (elements.length == 2) {
                pw.print(elements[0] + " or " + elements[1] + " ");
            } else {
                for (int i = 0; i < elements.length; i++) {
                    pw.print(elements[i]);

                    if (i < elements.length - 2) {
                        pw.print(", ");
                    } else if (i == elements.length - 2) {
                        pw.print(" or ");
                    }
                }

                pw.print(" ");
            }

            return; // enumeration is always terminal
        }

        // NODE KEYWORD PRINTED
        pw.print(node.getKeyword() + " ");

        HashMap links = node.getLinks(); // get hashmap of links for current node
        Set keys = links.keySet(); // save the initial set of links

        //***********************************************
        // Rule 2: sort links as follows: 1st is-a, 2nd cardinality, 3rd remaining
        HashMap isaLinks = new HashMap();
        HashMap compLinks = new HashMap();
        HashMap cardLinks = new HashMap();
        HashMap allLinks = new HashMap();
        HashMap remLinks = new HashMap();

        NLLink isaLink = new NLLink("", NLVisitor.LINK_SUBCLASS);

        HashMap newIsaLinks = new HashMap();
        for (Iterator iter = new HashSet(keys).iterator(); iter.hasNext();) {
            // cycle through each link
            NLLink link = (NLLink) iter.next();
            NLNode target = (NLNode) links.get(link);

            System.out.println("LINK: " + link.keyword + " (" + link.linkType + "), TARGET: " + target.keyword);

            if (link.equals(isaLink)) {
                newIsaLinks.put(link, target);
            }

            if (link.equals(isaLink) && target.links.keySet().size() == 0) {
                System.out.println(target.keyword + " " + node.keyword);

                // simply is-a link and not a nested restriction
                isaLinks.put(link, target);
            } else if (link.getLinkType().equals(NLVisitor.LINK_COMPLEMENT)) {
                compLinks.put(link, target);
            } else if (link.getLinkType().equals(NLVisitor.LINK_CARD)
                    || link.getLinkType().equals(NLVisitor.LINK_MAXCARD)
                    || link.getLinkType().equals(NLVisitor.LINK_MINCARD)) {
                // cardinality restrictions
                cardLinks.put(link, target);
            } else if (link.getLinkType().equals(NLVisitor.LINK_ALLVALUES)) {
                // all values restrictions - this also never happens
                allLinks.put(link, target);

            } else {
                // value restrictions
                remLinks.put(link, target);
            }
        }

        List sortedLinkNodes = new ArrayList();
        putLinks(sortedLinkNodes, isaLinks);
        putLinks(sortedLinkNodes, compLinks);
        putLinks(sortedLinkNodes, cardLinks);
        putLinks(sortedLinkNodes, remLinks);
        putLinks(sortedLinkNodes, allLinks);

        boolean bigMultiple = sortedLinkNodes.size() > 0;
        boolean bigComplex = false;

        if (bigMultiple) {
            for (int i = 0; i < sortedLinkNodes.size(); i++) {
                NLLink link = ((NLLinkNode) sortedLinkNodes.get(i)).link;
                // Check for just existential restriction or is_a
                if (!link.linkType.equals(NLVisitor.LINK_SOMEVALUES)
                        && !link.linkType.equals(NLVisitor.LINK_HASVALUE)
                        && !link.linkType.equals(NLVisitor.LINK_SUBCLASS)) {
                    bigComplex = true;
                    break;
                }
            }
        }

        boolean bigFirstTime = true;
        for (int i = 0; i < sortedLinkNodes.size(); i++) {

            if (bigComplex && bigFirstTime) {
                pw.print("that: \n");
                pw.print("\t");

                for (int j = 0; j < indent; j++) {
                    pw.print("\t");
                }

                pw.print("-- ");
            }

            // cycle through each sorted link
            NLLink link = ((NLLinkNode) sortedLinkNodes.get(i)).link;
            String linkProp = link.getKeyword();

            List linkTargets = new ArrayList();

            //***********************************************
            // Rule 3: find all targets that match the same link property
            // and put them in a shortened list = linkTargets
            for (int j = 0; j < sortedLinkNodes.size(); j++) {
                NLLink chkLink = ((NLLinkNode) sortedLinkNodes.get(j)).link;
                if (keys.contains(chkLink) && chkLink.getKeyword().equals(linkProp)) {
                    // add link target to common matrix
                    NLNode target = ((NLLinkNode) sortedLinkNodes.get(j)).node;
                    linkTargets.add(new NLLinkNode(chkLink, target));
                    keys.remove(chkLink); // successively remove to prevent duplicate printing of the exact same link
                }
            }

            //***********************************************
            // Rule 4: combine restrictions on the same property (e.g. allValues with a cardinality, either with a someValues etc)
            // and change linktype text for restrictions accordingly         
            List minLNs = filterLinks(linkTargets, NLVisitor.LINK_MINCARD, false);
            List maxLNs = filterLinks(linkTargets, NLVisitor.LINK_MAXCARD, false);
            if (minLNs.size() > 0 && maxLNs.size() > 0) {
                // assuming there is only one of each (can there be more?)
                NLLinkNode minCardLN = (NLLinkNode) minLNs.iterator().next();
                NLLinkNode maxCardLN = (NLLinkNode) maxLNs.iterator().next();
                // then combine both the min and max cardinalities using 'between'
                minCardLN.link.setLinkType(NLVisitor.LINK_BETWEEN);
                minCardLN.node.setKeyword(minCardLN.node.keyword + " - " + maxCardLN.node.keyword);
                linkTargets.remove(maxCardLN);
            }
            List cardLNs = filterLinks(linkTargets, NLVisitor.LINK_CARD, false);
            List someLNs = filterLinks(linkTargets, NLVisitor.LINK_SOMEVALUES, false);
            List allLNs = filterLinks(linkTargets, NLVisitor.LINK_ALLVALUES, false);
            List hasLNs = filterLinks(linkTargets, NLVisitor.LINK_HASVALUE, false);
            if (cardLNs.size() > 0 || minLNs.size() > 0 || maxLNs.size() > 0) {
                // combine cardinality with someValues
                for (Iterator k = someLNs.iterator(); k.hasNext();) {
                    NLLinkNode someLN = (NLLinkNode) k.next();
                    someLN.link.setLinkType("at least one of which is");
                }
                // combine cardinality with allValues - 
                // TODO now this will never happen?
                for (Iterator k = allLNs.iterator(); k.hasNext();) {
                    NLLinkNode allLN = (NLLinkNode) k.next();
                    allLN.link.setLinkType("each of which is");
                }
                // combine cardinality with oneOf
                for (Iterator k = hasLNs.iterator(); k.hasNext();) {
                    //System.out.println( "YOYOYO" );
                    NLLinkNode hasLN = (NLLinkNode) k.next();
                    //hasLN.link.setLinkType("that has");
                    hasLN.link.setLinkType("which is");
                }
            }
            // combine someValues with allValues
            // some x, some y and always x or y
            if (someLNs.size() > 0 && allLNs.size() > 0) {
                //TODO   
            }

            // START PRINTING
            //***********************************************
            // insert either-or for hasValue for oneOfs
            // code below ONLY FOR ONEOFs
            List oneofs = filterLinks(linkTargets, NLVisitor.LINK_ONEOF, true);
            if (oneofs.size() > 0) {

                String connector1 = "", connector2 = "";
                if (((NLLinkNode) oneofs.get(0)).link.isComplement) {
                    connector1 = " neither ";
                    connector2 = " nor ";
                } else {
                    connector1 = " either ";
                    connector2 = " or ";
                }

                // assume link.keyword starts with is/has
                String prefix = this.getPart(link.getKeyword(), true);
                pw.print(prefix + " ");

                if (!prefix.equals("has")) {
                    if (oneofs.size() > 1) {
                        //pw.print(" that"+connector1);
                        pw.print(connector1);
                    }
                    pw.print(this.getPart(link.getKeyword(), false) + " ");
                } else {
                    if (oneofs.size() > 1) {
                        //pw.print(" that"+connector1);
                        pw.print(connector1);
                    }
                }

                Vector v = new Vector(oneofs);
                for (int k = 0; k < v.size(); k++) {
                    NLNode target = ((NLLinkNode) v.get(k)).node;
                    printNode(target, false, indent);
                    if (v.size() > 2) {
                        if (k < v.size() - 2) {
                            pw.print(", ");
                        } else if (k < v.size() - 1) {
                            pw.print(connector2);
                        }
                    } else {
                        if (k == 0) {
                            pw.print(connector2);
                        }
                    }
                }

                //            for (Iterator j = oneofs.iterator(); j.hasNext(); ) {
                //               NLNode target = ((NLLinkNode) j.next()).node;
                //               printNode( target, false, indent );
                //               if ( j.hasNext() ) 
                //                   pw.print(connector2);               
                //            }

                if (prefix.equals("has")) {
                    pw.print(this.getPart(link.getKeyword(), false) + " ");
                }

                //if (linkTargets.size()>1) pw.print(", ");
                //else if (linkTargets.size()==1) pw.print(" and ");
            }

            // ***********************************************
            // insert either-or for UNIONS 
            List unions = filterLinks(linkTargets, NLVisitor.LINK_UNION, true);
            if (unions.size() > 0) {

                String connector1 = "", connector2 = "";
                if (((NLLinkNode) unions.get(0)).link.isComplement) {
                    connector1 = " neither ";
                    connector2 = " nor a ";
                } else {
                    connector1 = " either ";
                    connector2 = " or a ";
                }

                if (unions.size() > 1)
                    pw.print(" is" + connector1 + "a ");
                pw.print(link.getKeyword() + " ");
                for (Iterator j = unions.iterator(); j.hasNext();) {
                    NLNode target = ((NLLinkNode) j.next()).node;
                    printNode(target, false, indent);
                    if (j.hasNext())
                        pw.print(connector2);
                }

                if (linkTargets.size() > 1)
                    pw.print(", ");
                else if (linkTargets.size() == 1)
                    pw.print(" and ");
            }

            //System.out.println( "Here we are!!!" );

            //****************************************************************
            // finally handle remaining link-nodes normally   - EVERYTHING ELSE      
            boolean firstTime = true;
            boolean complex = false;
            boolean multiple = linkTargets.size() > 0;

            for (int j = 0; j < linkTargets.size(); j++) {
                NLLink flink = ((NLLinkNode) linkTargets.get(j)).link;
                ;
                NLNode ftarget = ((NLLinkNode) linkTargets.get(j)).node;

                if (flink.getLinkType().equals(NLVisitor.LINK_SOMEVALUES)) {

                } else {
                    complex = true;
                    System.out.println("complex!");
                }
            }

            //System.out.println( "THE CRITICAL FACTOR: " + (linkTargets.size() > 2) );

            boolean cardPrinted = false;
            for (int j = 0; j < linkTargets.size(); j++) {
                NLLink flink = ((NLLinkNode) linkTargets.get(j)).link;
                ;
                NLNode ftarget = ((NLLinkNode) linkTargets.get(j)).node;

                System.out.println("LOOP + " + i + ": " + flink.keyword + " (" + flink.linkType + "), TARGET: "
                        + ftarget.keyword);

                if (cardPrinted) {
                    System.out.println("CARD-IN-AL-ITY");
                    pw.print(", ");
                    pw.print(flink.getLinkType() + " ");

                    printNode(ftarget, firstTime, indent);

                } else {
                    if (flink.getLinkType().equals(NLVisitor.LINK_CARD)
                            || flink.getLinkType().equals(NLVisitor.LINK_MINCARD)
                            || flink.getLinkType().equals(NLVisitor.LINK_MAXCARD)
                            || flink.getLinkType().equals(NLVisitor.LINK_BETWEEN)) {
                        // change order for property and value for cardinality restriction
                        // re-phrase cardinality clauses , 
                        // e.g., with at most 1 is made from grape -> which is made from at most 1 grape                              
                        reOrderCardClause(flink, ftarget, link.getKeyword(), !firstTime, indent);
                        cardPrinted = true;
                    } else {
                        if (bigFirstTime) {
                            pw.print(flink.getLinkType() + " ");
                        }

                        if (complex && keys.size() > 0) {
                            //pw.print( "\n\t- " );
                        }

                        String prefix = this.getPart(link.getKeyword(), true);
                        if (prefix.equals("has")) { // TODO replace with function isPrefixEligible()
                            pw.print(prefix + " ");
                        } else {
                            pw.print(flink.getKeyword() + " ");
                        }

                        // make a check here for boolean properties
                        // example  is hard working "false" -> is not hard working
                        if (ftarget.keyword.indexOf("false") == -1 && ftarget.keyword.indexOf("true") == -1) {
                            // it is NOT a boolean property
                            // THIS IS WHERE MOST PRINTING TAKES PLACE
                            printNode(ftarget, firstTime, indent); // TODO NEW CHANGE - is this good?

                        } else {
                            // it is a boolean property
                            if (ftarget.keyword.indexOf("false") != -1) {
                                pw.print("not "); //TODO make negation better
                            }

                        }

                        if (prefix.equals("has")) { // TODO replace with function isPrefixEligible()
                            pw.print(this.getPart(link.getKeyword(), false) + " ");
                        }
                    }
                }

                // If complex sentence forming, break 
                // Currently complex is determined as those that involve
                // combination of two restrictions (eg. card + all/some/has)
                // and all these combinations use the keyword "which"
                //TODO Extend and improve determination of complex
                //if ( flink.getLinkType().indexOf("that") != -1 && keys.size()>0 ) {
                if (complex && keys.size() > 0) {
                    //pw.print( "\n\t- " );
                    // TODO fix this later
                } else if (!cardPrinted) {
                    // else add comma or and depending on number of items left
                    if (j < linkTargets.size() - 2)
                        pw.print(", ");
                    else if (j == linkTargets.size() - 2)
                        pw.print(" and ");
                }

                firstTime = false;
            }

            // END INTERNAL LOOP

            if (!bigComplex && keys.size() > 0) {
                if (keys.size() == 1)
                    pw.print(" and ");
                else
                    pw.print(", ");
            } else {
                if (keys.size() == 0) {
                    //pw.print( ". " );
                } else {
                    pw.print("\n\t-- ");
                }
            }

            bigFirstTime = false;
        }
    }

    //   consider that only the last word of the keyword is a noun phrase
    private void reOrderCardClause(NLLink flink, NLNode ftarget, String strKeyword, boolean onlyonce, int indent) {

        //split the strings into tokens
        String[] tokens = strKeyword.split("( )+");
        String[] tags = tagger.tag(tokens);
        int nounPhraseIndex = -1;

        //check where the NP starts, assume it starts with the first noun
        for (int i = 0; i < tags.length; i++) {
            if (tags[i].equals("NN") || tags[i].equals("NNS") || tags[i].equals("NP") || tags[i].equals("NPS")) { // noun phrase
                nounPhraseIndex = i;
            }
        }
        String strPrefix = "", strNP = "";
        //case when there is no NP - select the last token as target and the rest as prefix      
        if (nounPhraseIndex == -1)
            nounPhraseIndex = tokens.length - 1;

        for (int i = 0; i < nounPhraseIndex; i++) {
            strPrefix += (tokens[i] + " ");
        }
        for (int i = nounPhraseIndex; i < tokens.length; i++) {
            strNP += (tokens[i] + " ");
        }

        // change - removed all that's from below code
        if (flink.getLinkType().equals(NLVisitor.LINK_CARD)) { //with exactly
            pw.print(" " + strPrefix + " exactly ");
        } else if (flink.getLinkType().equals(NLVisitor.LINK_MINCARD)) { //with at most 
            pw.print(" " + strPrefix + " at least ");
        } else if (flink.getLinkType().equals(NLVisitor.LINK_MAXCARD)) { //with at least
            pw.print(" " + strPrefix + " at most ");
        } else if (flink.getLinkType().equals(NLVisitor.LINK_BETWEEN)) { //with between
            pw.print(" " + strPrefix + " between ");
        }

        printNode(ftarget, false, indent); // TODO did this matter?
        if (!onlyonce)
            pw.print(strNP);
    }

    private List filterLinks(List linkTargets, String filterType, boolean doRemoveLinks) {

        List filter = new ArrayList();
        Set remove = new HashSet();
        for (int i = 0; i < linkTargets.size(); i++) {
            NLLink link = ((NLLinkNode) linkTargets.get(i)).link;
            String linkType = link.getLinkType();
            if (linkType.equals(filterType)) {
                remove.add(linkTargets.get(i));
                filter.add(((NLLinkNode) linkTargets.get(i)));
            }
        }
        if (doRemoveLinks)
            linkTargets.removeAll(remove);
        return filter;
    }

    protected class NLTree {
        // natural language tree to represent entity structure
        private NLNode root;

        public NLTree() {
        }

        public NLTree(NLNode root) {
            this.root = root;
        }

        public void setRoot(NLNode root) {
            this.root = root;
        }

        public NLNode getRoot() {
            return root;
        }
    }

    protected class NLNode {
        // structure of a single NL node
        private String keyword;
        private HashMap links;
        public int nodeType = 0;

        // 0 is for Class
        // 1 is for Cardinality Target
        // 2 is for Data Literal
        // 3 is for Individual
        // 4 is for dummy node for enumeration - always called **enum**

        public NLNode(String word, int type) {
            keyword = word;
            links = new HashMap();
            nodeType = type;
        }

        public String getKeyword() {
            return keyword;
        }

        public void setKeyword(String word) {
            this.keyword = word;
        }

        public NLNode addLink(NLLink link, NLNode target) {

            for (Iterator iter = links.keySet().iterator(); iter.hasNext();) {
                NLLink key = (NLLink) iter.next();
                if (key.equals(link)) {
                    NLNode value = (NLNode) links.get(key);
                    if (value.equals(target)) {
                        return value;
                    }
                }
            }

            NLNode newTarget = new NLNode(target.getKeyword(), target.nodeType);
            links.put(new NLLink(link.getKeyword(), link.getLinkType(), link.isComplement), newTarget);
            // also remove complement on link if present
            link.setIsComplement(false);
            return newTarget;
        }

        public HashMap getLinks() {
            return links;
        }

        public boolean equals(NLNode node) {
            if (node.getKeyword().equals(this.keyword))
                return true;
            else
                return false;
        }
    }

    protected class NLLink {
        // different types of links between nodes
        private String linkType;
        private String keyword;
        private boolean isComplement;

        public NLLink(String keyword, String type) {
            this.keyword = keyword;
            this.linkType = type;
            this.isComplement = false;
        }

        public NLLink(String keyword, String type, boolean isComplement) {
            this.keyword = keyword;
            this.linkType = type;
            this.isComplement = isComplement;
        }

        public boolean isComplement() {
            return this.isComplement;
        }

        public void setIsComplement(boolean mode) {
            this.isComplement = mode;
        }

        public String getLinkType() {
            return linkType;
        }

        public void setLinkType(String linkType) {
            this.linkType = linkType;
        }

        public void setKeyword(String keyword) {
            this.keyword = keyword;
        }

        public String getKeyword() {
            return keyword;
        }

        public boolean equals(NLLink link) {

            if ((link.getKeyword().equals(this.keyword)) && link.getLinkType().equals(this.getLinkType())
                    && link.isComplement() == this.isComplement())
                return true;
            else
                return false;
        }

        public void printLink() {
            System.out.println("LINK: (" + this.linkType + ") " + this.keyword);
        }
    }

    protected class NLLinkNode {
        NLLink link;
        NLNode node;

        public NLLinkNode(NLLink link, NLNode node) {
            this.link = link;
            this.node = node;
        }
    }

    /* (non-Javadoc)
     * @see org.semanticweb.owl.model.OWLObjectVisitor#visit(org.semanticweb.owl.model.OWLFunctionalPropertyAxiom)
     */
    public void visit(OWLFunctionalPropertyAxiom node) throws OWLException {
        // TODO Auto-generated method stub

    }

    /* (non-Javadoc)
     * @see org.semanticweb.owl.model.OWLObjectVisitor#visit(org.semanticweb.owl.model.OWLInverseFunctionalPropertyAxiom)
     */
    public void visit(OWLInverseFunctionalPropertyAxiom node) throws OWLException {
        // TODO Auto-generated method stub

    }

    /* (non-Javadoc)
     * @see org.semanticweb.owl.model.OWLObjectVisitor#visit(org.semanticweb.owl.model.OWLTransitivePropertyAxiom)
     */
    public void visit(OWLTransitivePropertyAxiom node) throws OWLException {
        // TODO Auto-generated method stub

    }

    /* (non-Javadoc)
     * @see org.semanticweb.owl.model.OWLObjectVisitor#visit(org.semanticweb.owl.model.OWLSymmetricPropertyAxiom)
     */
    public void visit(OWLSymmetricPropertyAxiom node) throws OWLException {
        // TODO Auto-generated method stub

    }

    /* (non-Javadoc)
     * @see org.semanticweb.owl.model.OWLObjectVisitor#visit(org.semanticweb.owl.model.OWLInversePropertyAxiom)
     */
    public void visit(OWLInversePropertyAxiom node) throws OWLException {
        // TODO Auto-generated method stub

    }

    /* (non-Javadoc)
     * @see org.semanticweb.owl.model.OWLObjectVisitor#visit(org.semanticweb.owl.model.OWLPropertyDomainAxiom)
     */
    public void visit(OWLPropertyDomainAxiom node) throws OWLException {
        // TODO Auto-generated method stub

    }

    /* (non-Javadoc)
     * @see org.semanticweb.owl.model.OWLObjectVisitor#visit(org.semanticweb.owl.model.OWLObjectPropertyRangeAxiom)
     */
    public void visit(OWLObjectPropertyRangeAxiom node) throws OWLException {
        // TODO Auto-generated method stub

    }

    /* (non-Javadoc)
     * @see org.semanticweb.owl.model.OWLObjectVisitor#visit(org.semanticweb.owl.model.OWLDataPropertyRangeAxiom)
     */
    public void visit(OWLDataPropertyRangeAxiom node) throws OWLException {
        // TODO Auto-generated method stub

    }

    /* (non-Javadoc)
     * @see org.semanticweb.owl.model.OWLObjectVisitor#visit(org.semanticweb.owl.model.OWLObjectPropertyInstance)
     */
    public void visit(OWLObjectPropertyInstance node) throws OWLException {
        //prefixDomain(node.getProperty());
        linkContext.setLinkType(LINK_SOMEVALUES);
        node.getProperty().accept(this);
        node.getObject().accept(this);
    }

    /* (non-Javadoc)
     * @see org.semanticweb.owl.model.OWLObjectVisitor#visit(org.semanticweb.owl.model.OWLDataPropertyInstance)
     */
    public void visit(OWLDataPropertyInstance node) throws OWLException {
        // TODO Auto-generated method stub

    }

    /* (non-Javadoc)
     * @see org.semanticweb.owl.model.OWLObjectVisitor#visit(org.semanticweb.owl.model.OWLIndividualTypeAssertion)
     */
    public void visit(OWLIndividualTypeAssertion node) throws OWLException {
        // TODO Auto-generated method stub

    }
}