org.matonto.rdf.orm.generate.SourceGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.matonto.rdf.orm.generate.SourceGenerator.java

Source

package org.matonto.rdf.orm.generate;

/*-
 * #%L
 * rdf.orm.generate
 * $Id:$
 * $HeadURL:$
 * %%
 * Copyright (C) 2016 iNovex Information Systems, Inc.
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 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 Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */

import aQute.bnd.annotation.component.Reference;
import com.sun.codemodel.*;
import org.apache.commons.lang3.StringUtils;
import org.matonto.rdf.api.ModelFactory;
import org.matonto.rdf.api.Value;
import org.matonto.rdf.api.ValueFactory;
import org.matonto.rdf.orm.AbstractOrmFactory;
import org.matonto.rdf.orm.OrmException;
import org.matonto.rdf.orm.OrmFactory;
import org.matonto.rdf.orm.Thing;
import org.matonto.rdf.orm.conversion.ValueConverter;
import org.matonto.rdf.orm.conversion.ValueConverterRegistry;
import org.matonto.rdf.orm.impl.ThingImpl;
import org.openrdf.model.IRI;
import org.openrdf.model.Model;
import org.openrdf.model.Resource;
import org.openrdf.model.impl.LinkedHashModel;
import org.openrdf.model.vocabulary.OWL;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.model.vocabulary.RDFS;
import org.openrdf.model.vocabulary.XMLSchema;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class SourceGenerator {

    private static final Logger LOG = LoggerFactory.getLogger(SourceGenerator.class);

    private static final String CLASS_TYPE_IRI_FIELD = "TYPE";

    private static final String DEFAULT_IMPL_FIELD = "DEFAULT_IMPL";

    private final String ontologyName;
    private final JCodeModel codeModel = new JCodeModel();
    private final Model metaModel;
    private final Model model;
    private final String packageName;
    private Collection<IRI> classIris;
    private Map<IRI, JDefinedClass> interfaces = new HashMap<>();
    private Map<JDefinedClass, Map<String, IRI>> interfaceFieldIriMap = new HashMap<>();
    private Map<JDefinedClass, Map<String, IRI>> interfaceFieldRangeMap = new HashMap<>();
    private Map<JDefinedClass, Map<JMethod, JFieldVar>> classMethodIriMap = new HashMap<>();
    private Map<JDefinedClass, JDefinedClass> interfaceImplMap = new HashMap<>();
    private final List<ReferenceOntology> referenceOntologies = new ArrayList<>();

    public SourceGenerator(final Model ontologyGraph, final String outputPackage,
            final Collection<ReferenceOntology> referenceOntologies) throws OntologyToJavaException, IOException {
        this(ontologyGraph, outputPackage, null, referenceOntologies);
    }

    public SourceGenerator(final Model ontologyGraph, final String outputPackage, final String ontologyName,
            final Collection<ReferenceOntology> referenceOntologies) throws OntologyToJavaException, IOException {
        this.ontologyName = ontologyName;
        if (referenceOntologies != null) {
            this.referenceOntologies.addAll(referenceOntologies);
        }
        this.model = ontologyGraph;
        this.metaModel = new LinkedHashModel(model);
        this.packageName = outputPackage;
        // LOG if we're not referencing an imported ontology.
        checkImports(this.model, this.referenceOntologies);
        this.referenceOntologies.forEach(ont -> {
            checkImports(ont.getOntologyModel(), this.referenceOntologies);
            this.metaModel.addAll(ont.getOntologyModel());
        });
        // Built interfaces...
        generateIndividualInterfaces();
        // Link the interfaces inheritence-wise.
        linkIndividualInterfaces();
        // Populate each individual interface with the required accessors.
        populateInterfacesWithMethods();
        // Build Impls.
        generateImplementations();
        // Build factories.
        generateFactories();
    }

    /**
     * Simple method to LOG some warnings if we aren't importing referenced ontologies.
     */
    private static void checkImports(final Model checkingModel, final List<ReferenceOntology> referenceOntologies) {
        checkingModel.filter(null, OWL.IMPORTS, null).stream().forEach(stmt -> {
            boolean contains = false;
            for (ReferenceOntology refOnt : referenceOntologies) {
                Optional<org.openrdf.model.Resource> resource = refOnt.getOntologyModel()
                        .filter(null, RDF.TYPE, OWL.ONTOLOGY).stream()
                        .filter(ontStmt -> ontStmt.getSubject().equals(stmt.getObject()))
                        .map(ontStmt -> ontStmt.getSubject()).findFirst();
                if (resource.isPresent()) {
                    contains = true;
                    break;
                }
            }
            if (!contains) {
                LOG.warn(String.format(
                        "Potential error: Generate ontology '%s' specifies that it imports '%s', but it isn't referenced",
                        stmt.getSubject().stringValue(), stmt.getObject().stringValue()));
            }
        });
    }

    public static void toSource(final Model model, final String outputPackage, final String location,
            final Collection<ReferenceOntology> referencedOntologies) throws OntologyToJavaException, IOException {
        toSource(model, outputPackage, null, location, referencedOntologies);
    }

    public static void toSource(final Model model, final String outputPackage, final String name,
            final String location, final Collection<ReferenceOntology> referencedOntologies)
            throws OntologyToJavaException, IOException {
        final SourceGenerator generator = new SourceGenerator(model, outputPackage, name, referencedOntologies);
        generator.build(location);
    }

    /**
     * Generate a method name from a given static field name.
     *
     * @param prefix
     * @param staticFieldName
     * @return
     */
    private static String generateMethodName(final String prefix, final String staticFieldName) {
        return prefix + StringUtils.capitalize(staticFieldName.substring(0, staticFieldName.length() - 4));
    }

    /**
     * This method will simply convert a given IRI to its name by pulling off
     * the end of the IRI.
     *
     * @param capitalize Whether or not to capitalize the first letter of the name
     * @param iri        The {@link IRI} to process
     * @param model      The {@link Model} to process statements from (should contain
     *                   the ontology)
     * @return The name of the field we'll use
     */
    static String getName(final boolean capitalize, final IRI iri, final Model model) {
        final String classIriString = iri.stringValue();
        String className = classIriString.contains("#")
                ? classIriString.substring(classIriString.lastIndexOf('#') + 1)
                : classIriString.contains("/") ? classIriString.substring(classIriString.lastIndexOf('/') + 1)
                        : null;
        if (className != null) {
            className = stripWhiteSpace(className.trim());
            className = (capitalize ? StringUtils.capitalize(className) : StringUtils.uncapitalize(className));
        }
        return className;
    }

    /**
     * Simple method to strip whitespaces from the name. It will also ensure it
     * is a valid class or field name.
     *
     * @param input The input string
     * @return The stripped and cleaned output name
     */
    protected static String stripWhiteSpace(final String input) {
        StringBuilder builder = new StringBuilder();
        boolean lastIsWhiteSpace = false;
        boolean first = true;
        for (char c : input.toCharArray()) {
            if (first && !Character.isJavaIdentifierStart(c) && Character.isJavaIdentifierPart(c)) {
                builder.append("_");
                builder.append(c);
                first = false;
            } else if (Character.isWhitespace(c)) {
                lastIsWhiteSpace = true;
            } else if (Character.isJavaIdentifierPart(c)) {
                builder.append(lastIsWhiteSpace ? StringUtils.capitalize(c + "") : c);
                lastIsWhiteSpace = false;
                first = false;
            }
        }
        return builder.toString();
    }

    /**
     * Build the code.
     *
     * @param path
     * @throws IOException
     */
    private void build(final String path) throws IOException {
        File output = new File(path);
        output.mkdirs();
        codeModel.build(output);
    }

    private void generateFactories() throws OntologyToJavaException {
        final Collection<String> issues = new ArrayList<>();
        interfaceImplMap.forEach((interfaze, clazz) -> {
            final String factoryName = interfaze.name() + "Factory";
            try {
                final IRI id = iriFromInterface(interfaze);
                final JDefinedClass factory = codeModel._class(JMod.PUBLIC, packageName + "." + factoryName,
                        ClassType.CLASS);
                factory.javadoc()
                        .add("This {@link org.matonto.rdf.orm.OrmFactory} implementation will construct "
                                + interfaze.name() + " objects.  It will be published as an OSGi service.  "
                                + (id != null ? "See " + id.stringValue() + " for more information." : ""));
                factory._extends(codeModel.ref(AbstractOrmFactory.class).narrow(interfaze));
                factory.annotate(aQute.bnd.annotation.component.Component.class).paramArray("provide")
                        .param(OrmFactory.class).param(ValueConverter.class).param(factory.dotclass());
                factory.constructor(JMod.PUBLIC).body().invoke("super").arg(JExpr.dotclass(interfaze))
                        .arg(JExpr.dotclass(clazz));

                final JMethod getExisting = factory.method(JMod.PUBLIC, interfaze, "getExisting");
                getExisting.annotate(Override.class);
                getExisting.body()
                        ._return(JExpr._new(clazz)
                                .arg(getExisting.param(org.matonto.rdf.api.Resource.class, "resource"))
                                .arg(getExisting.param(org.matonto.rdf.api.Model.class, "model"))
                                .arg(getExisting.param(org.matonto.rdf.api.ValueFactory.class, "valueFactory"))
                                .arg(getExisting.param(org.matonto.rdf.orm.conversion.ValueConverterRegistry.class,
                                        "valueConverterRegistry")));

                final JMethod getTypeIri = factory.method(JMod.PUBLIC, org.matonto.rdf.api.IRI.class, "getTypeIRI");
                getTypeIri.annotate(Override.class);
                getTypeIri.body()
                        ._return(JExpr.ref("valueFactory").invoke("createIRI").arg(interfaze.staticRef("TYPE")));

                // Get the parent type IRIs by adding a hash set of all the parent interface IRIs.
                final JMethod getParentTypeIRIs = factory.method(JMod.PUBLIC,
                        codeModel.ref(Set.class).narrow(org.matonto.rdf.api.IRI.class), "getParentTypeIRIs");
                getParentTypeIRIs.annotate(Override.class);
                JBlock body = getParentTypeIRIs.body();
                JVar set = body.decl(JMod.FINAL, codeModel.ref(Set.class).narrow(org.matonto.rdf.api.IRI.class),
                        "set", JExpr._new(codeModel.ref(HashSet.class).narrow(org.matonto.rdf.api.IRI.class)));
                Set<JClass> tracking = new HashSet<JClass>();
                tracking.add(codeModel.ref(Thing.class));
                recurseAddParentTypeIris(interfaze, body, set, tracking);
                body._return(JExpr.ref("set"));

                final JMethod setModelFactory = factory.method(JMod.PUBLIC, codeModel.VOID, "setModelFactory");
                setModelFactory.annotate(Override.class);
                setModelFactory.annotate(Reference.class);
                JVar modelFactoryParam = setModelFactory.param(ModelFactory.class, "modelFactory");
                setModelFactory.body().assign(JExpr._this().ref("modelFactory"), modelFactoryParam);

                final JMethod setValueFactory = factory.method(JMod.PUBLIC, codeModel.VOID, "setValueFactory");
                setValueFactory.annotate(Override.class);
                setValueFactory.annotate(Reference.class);
                JVar valueFactoryParam = setValueFactory.param(ValueFactory.class, "valueFactory");
                setValueFactory.body().assign(JExpr._this().ref("valueFactory"), valueFactoryParam);

                final JMethod setValueConverterRegistry = factory.method(JMod.PUBLIC, codeModel.VOID,
                        "setValueConverterRegistry");
                setValueConverterRegistry.annotate(Override.class);
                setValueConverterRegistry.annotate(Reference.class);
                JVar valueConverterRegistryParam = setValueConverterRegistry.param(ValueConverterRegistry.class,
                        "valueConverterRegistry");
                setValueConverterRegistry.body().assign(JExpr._this().ref("valueConverterRegistry"),
                        valueConverterRegistryParam);

            } catch (final Exception e) {
                issues.add("Issue generating factory class: " + factoryName + ": " + e.getMessage());
            }
        });
        if (!issues.isEmpty()) {
            throw new OntologyToJavaException(
                    "Could not generate POJOs from ontology due to the following issues:\n\t"
                            + StringUtils.join(issues, "\n\t") + "\n\n");
        }
    }

    private void recurseAddParentTypeIris(JClass interfaze, JBlock body, JVar set, Set<JClass> alreadyHas) {
        interfaze._implements().forEachRemaining(item -> {
            if (!alreadyHas.contains(item)) {
                body.add(set.invoke("add")
                        .arg(JExpr.ref("valueFactory").invoke("createIRI").arg(item.staticRef("TYPE"))));
                alreadyHas.add(item);
                recurseAddParentTypeIris(item, body, set, alreadyHas);
            }
        });
    }

    private void recurseImplementations(final JDefinedClass impl, final JClass interfaceClass) {
        // implement the supplied interface class.
        impl._implements(interfaceClass);
        if (interfaceClass instanceof JDefinedClass) {
            // If the interface is a JDefinedClass (will be), then we'll recurse
            // up the lineage.
            generateFieldAccessorsForEachInterfaceMethod(impl, (JDefinedClass) interfaceClass);
            interfaceClass._implements().forEachRemaining(parentInterface -> {
                recurseImplementations(impl, parentInterface);
            });
        }
    }

    private void generateImplementations() throws OntologyToJavaException {
        final Collection<String> issues = new ArrayList<>();
        interfaces.forEach((classIri, interfaceClass) -> {
            if (classIri != null) {
                try {
                    /*
                     * Define the implementation class, wire it to the correct
                     * interface and extend Thing.
                     */
                    final JDefinedClass impl = codeModel._class(JMod.PUBLIC,
                            packageName + "." + interfaceClass.name() + "Impl", ClassType.CLASS);
                    interfaceImplMap.put(interfaceClass, impl);
                    impl._extends(codeModel.ref(ThingImpl.class));
                    impl._implements(interfaceClass);
                    impl.javadoc().add("This implementation of the '" + classIri.stringValue()
                            + "' entity will allow developers to work in native java POJOs.");
                    // Constructors - call super from Thing.
                    generateImplConstructors(impl, interfaceClass);
                    recurseImplementations(impl, interfaceClass);

                    // Generate default impl.
                    interfaceClass
                            .field(JMod.PUBLIC | JMod.STATIC | JMod.FINAL,
                                    codeModel.ref(Class.class).narrow(interfaceClass.wildcard()),
                                    DEFAULT_IMPL_FIELD, impl.dotclass())
                            .javadoc().add("The default implementation for this interface");
                } catch (Exception e) {
                    issues.add("Issue generating implementation for '" + classIri.stringValue() + "': "
                            + e.getMessage());
                }
            }
        });
        if (!issues.isEmpty()) {
            throw new OntologyToJavaException(
                    "Could not generate POJOs from ontology due to the following issues:\n\t"
                            + StringUtils.join(issues, "\n\t") + "\n\n");
        }
    }

    private void generateFieldAccessorsForEachInterfaceMethod(final JDefinedClass impl,
            final JDefinedClass interfaceClass) {
        interfaceClass.methods().forEach(interfaceMethod -> {
            LOG.debug(
                    "Adding " + interfaceMethod.name() + " to the implementation class: " + interfaceClass.name());
            // Generate getter.
            if (interfaceMethod.name().startsWith("get") || interfaceMethod.name().startsWith("is")) {
                generateFieldGetterForImpl(impl, interfaceMethod, interfaceClass);
            }
            // Generate setter.
            else if (interfaceMethod.name().startsWith("set")) {
                generateFieldSetterForImpl(impl, interfaceMethod, interfaceClass);
            }
        });
    }

    private void generateFieldSetterForImpl(final JDefinedClass impl, final JMethod interfaceMethod,
            final JDefinedClass interfaceClass) {
        if (impl.getMethod(interfaceMethod.name(), interfaceMethod.listParamTypes()) == null) {
            final JMethod method = impl.method(JMod.PUBLIC, interfaceMethod.type(), interfaceMethod.name());
            method.param(interfaceMethod.params().get(0).type(), "arg");
            method._throws(OrmException.class);
            method.annotate(Override.class);
            if (interfaceMethod.params().get(0).type().fullName().startsWith("java.util.Set")) {
                method.body().invoke("setProperties")
                        .arg(JExpr.ref("valueConverterRegistry").invoke("convertTypes")
                                .arg(interfaceMethod.params().get(0)).arg(JExpr._this()))
                        .arg(JExpr.ref("valueFactory").invoke("createIRI").arg(interfaceClass
                                .staticRef(classMethodIriMap.get(interfaceClass).get(interfaceMethod))));
            } else {
                method.body().invoke("setProperty")
                        .arg(JExpr.ref("valueConverterRegistry").invoke("convertType")
                                .arg(interfaceMethod.params().get(0)).arg(JExpr._this()))
                        .arg(JExpr.ref("valueFactory").invoke("createIRI").arg(interfaceClass
                                .staticRef(classMethodIriMap.get(interfaceClass).get(interfaceMethod))));
            }
            // TODO - add javadoc.
            // JDocComment jdoc = method.javadoc();
            // jdoc.add("");
        } else {
            LOG.warn("Avoided duplicate setter method: " + interfaceMethod.name() + " on class: " + impl.name());
        }
    }

    private void generateFieldGetterForImpl(final JDefinedClass impl, final JMethod interfaceMethod,
            final JDefinedClass interfaceClass) {
        if (impl.getMethod(interfaceMethod.name(), interfaceMethod.listParamTypes()) == null) {
            final JMethod method = impl.method(JMod.PUBLIC, interfaceMethod.type(), interfaceMethod.name());
            method._throws(OrmException.class);
            method.annotate(Override.class);
            // TODO - add javadoc.
            // JDocComment jdoc = method.javadoc();
            // jdoc.add("");
            convertValueBody(interfaceClass, interfaceMethod, impl, method);
        } else {
            LOG.warn("Avoided duplicate getter method: " + interfaceMethod.name() + " on class: " + impl.name());
        }
    }

    private void generateImplConstructors(final JDefinedClass impl, final JDefinedClass interfaceClazz) {
        final JMethod constructor = impl.constructor(JMod.PUBLIC);
        constructor.body().invoke("super")
                .arg(constructor.param(JMod.FINAL, org.matonto.rdf.api.Resource.class, "subjectIri"))
                .arg(constructor.param(JMod.FINAL, org.matonto.rdf.api.Model.class, "backingModel"))
                .arg(constructor.param(JMod.FINAL, ValueFactory.class, "valueFactory"))
                .arg(constructor.param(JMod.FINAL, ValueConverterRegistry.class, "valueConverterRegistry"));
        JDocComment basicDoc = constructor.javadoc();
        basicDoc.add("Construct a new " + interfaceClazz.name() + " with the subject IRI and the backing dataset");
        basicDoc.addParam("subjectIri").add("The subject of this " + interfaceClazz.name());
        basicDoc.addParam("valueFactory").add("The value factory to use for this " + interfaceClazz.name());
        basicDoc.addParam("backingModel").add("The backing dataset/model of this " + interfaceClazz.name());
        basicDoc.addParam("valueConverterRegistry")
                .add("The ValueConversionRegistry for this " + interfaceClazz.name());

        final JMethod constructor2 = impl.constructor(JMod.PUBLIC);
        constructor2.body().invoke("super").arg(constructor2.param(JMod.FINAL, String.class, "subjectIriStr"))
                .arg(constructor2.param(JMod.FINAL, org.matonto.rdf.api.Model.class, "backingModel"))
                .arg(constructor2.param(JMod.FINAL, ValueFactory.class, "valueFactory"))
                .arg(constructor2.param(JMod.FINAL, ValueConverterRegistry.class, "valueConversionRegistry"));
        JDocComment basicDoc2 = constructor2.javadoc();
        basicDoc2.add("Construct a new " + interfaceClazz.name() + " with the subject IRI and the backing dataset");
        basicDoc2.addParam("subjectIriStr").add("The subject of this " + interfaceClazz.name());
        basicDoc2.addParam("valueFactory").add("The value factory to use for this " + interfaceClazz.name());
        basicDoc2.addParam("backingModel").add("The backing dataset/model of this " + interfaceClazz.name());
        basicDoc2.addParam("valueConversionRegistry")
                .add("The ValueConversionRegistry for this " + interfaceClazz.name());

    }

    /**
     * Add getters and setters to the interfaces.
     */
    private void populateInterfacesWithMethods() {
        interfaces.forEach((iri, clazz) -> {
            final Map<JMethod, JFieldVar> methodIriMap = new HashMap<>();
            clazz.fields().forEach((name, fieldVar) -> {
                if (!name.equals(CLASS_TYPE_IRI_FIELD)) {
                    final IRI propertyIri = interfaceFieldIriMap.get(clazz).get(name);
                    final String fieldName = name.substring(0, name.length() - 4);

                    final JClass type = identifyType(propertyIri);

                    JClass getterType;
                    JClass setterType;

                    if (isPropertyFunctional(propertyIri)) {
                        getterType = codeModel.ref(Optional.class).narrow(type);
                        setterType = type;
                    } else {
                        getterType = codeModel.ref(Set.class).narrow(type);
                        setterType = codeModel.ref(Set.class).narrow(type);
                    }

                    if (type != null) {
                        methodIriMap.put(generateGetterMethodForInterface(clazz, iri, name, fieldName, propertyIri,
                                getterType), clazz.fields().get(fieldName + "_IRI"));
                        methodIriMap.put(generateSetterMethodForInterface(clazz, iri, name, fieldName, propertyIri,
                                setterType), clazz.fields().get(fieldName + "_IRI"));
                    } else {
                        // TODO - handle the type is undefined... Work with it
                        // as a Value?
                    }
                }
            });
            classMethodIriMap.put(clazz, methodIriMap);
        });
    }

    private JMethod generateGetterMethodForInterface(final JDefinedClass clazz, final IRI interfaceIri,
            final String name, final String fieldName, final IRI propertyIri, final JClass type) {
        final JMethod method = clazz.method(JMod.PUBLIC, type,
                generateMethodName(type.equals(boolean.class) ? "is" : "get", name));
        method._throws(OrmException.class);
        final JDocComment comment = method.javadoc();
        comment.add("Get the " + fieldName + " property from this instance of a "
                + (interfaceIri != null ? interfaceIri.stringValue()
                        : getOntologyName(this.packageName, this.ontologyName))
                + "' type.<br><br>" + getFieldComment(propertyIri));
        comment.addReturn().add("The " + fieldName + " {@link " + type.binaryName() + "} value for this instance");
        return method;
    }

    private JMethod generateSetterMethodForInterface(final JDefinedClass clazz, final IRI interfaceIri,
            final String name, final String fieldName, final IRI propertyIri, final JClass type) {
        final JMethod method = clazz.method(JMod.PUBLIC, codeModel.VOID, generateMethodName("set", name));
        method._throws(OrmException.class);
        method.param(type, "arg");
        // TODO - comments
        // final JDocComment comment = method.javadoc();
        // comment.add("Get the " + fieldName + " property from this instance of
        // a " + interfaceIri.stringValue()
        // + "' type.<br><br>" + getFieldComment(propertyIri));
        // comment.addReturn().add("The " + fieldName + " {@link " +
        // type.binaryName() + "} value for this instance");
        return method;
    }

    /**
     * TODO - make better?
     *
     * @param property The IRI of the property you want to figure the type out for.
     * @return The JClass representing the type of parameter the property specifies
     */
    private JClass identifyType(final IRI property) {
        final Set<org.openrdf.model.Value> objects = this.metaModel.filter(property, RDFS.RANGE, null).objects();
        if (objects.size() == 1) {
            final IRI rangeIri = (IRI) objects.iterator().next();
            // Handle our types.
            final JDefinedClass ourClass = interfaces.get(rangeIri);
            if (ourClass != null) {
                return codeModel.ref(ourClass.fullName());
            } else if (rangeIri.equals(RDFS.LITERAL)) {
                return codeModel.ref(org.matonto.rdf.api.Literal.class);
            } else if (rangeIri.equals(RDFS.RESOURCE)) {
                return codeModel.ref(org.matonto.rdf.api.Resource.class);
            } else if (rangeIri.equals(XMLSchema.ANYURI)) {
                return codeModel.ref(org.matonto.rdf.api.IRI.class);
            } else if (rangeIri.equals(XMLSchema.STRING)) {
                return codeModel.ref(String.class);
            } else if (rangeIri.equals(XMLSchema.BOOLEAN)) {
                return codeModel.ref(Boolean.class);
            } else if (rangeIri.equals(XMLSchema.BYTE)) {
                return codeModel.ref(Byte.class);
            } else if (rangeIri.equals(XMLSchema.DATE) || rangeIri.equals(XMLSchema.DATETIME)) {
                return codeModel.ref(Date.class);
            } else if (rangeIri.equals(XMLSchema.FLOAT)) {
                return codeModel.ref(Float.class);
            } else if (rangeIri.equals(XMLSchema.DOUBLE)) {
                return codeModel.ref(Double.class);
            } else if (rangeIri.equals(XMLSchema.LONG)) {
                return codeModel.ref(Long.class);
            } else if (rangeIri.equals(XMLSchema.INTEGER)) {
                return codeModel.ref(Integer.class);
            } else if (rangeIri.equals(OWL.THING)) {
                return codeModel.ref(Thing.class);
            } else {
                LOG.warn("ORM does not know what type to make properties of range '" + rangeIri.stringValue()
                        + "' so we'll use Optional<Value> or Set<Value>");
                // TODO - evaluate for NPE potential.
                return this.metaModel.filter(property, RDF.TYPE, null).objects().contains(OWL.OBJECTPROPERTY)
                        ? codeModel.ref(Thing.class)
                        : codeModel.ref(Value.class);
            }
        } else {
            // TODO - evaluate for NPE potential.
            LOG.warn("Property '" + property + "' "
                    + (objects.isEmpty() ? "doesn't specify a range." : "Specifies multiple ranges"));
            return this.metaModel.filter(property, RDF.TYPE, null).objects().contains(OWL.OBJECTPROPERTY)
                    ? codeModel.ref(Thing.class)
                    : codeModel.ref(Value.class);
        }
    }

    /**
     * Link the ontology generated interfaces together.
     *
     * @throws OntologyToJavaException
     */
    private void linkIndividualInterfaces() throws OntologyToJavaException {
        final List<String> issues = new ArrayList<>();
        interfaces.forEach((iri, clazz) -> {
            if (iri != null) {
                model.filter(iri, RDFS.SUBCLASSOF, null).forEach(stmt -> {
                    org.openrdf.model.Value value = stmt.getObject();
                    if (value instanceof IRI) {
                        final IRI extending = (IRI) stmt.getObject();
                        if (!extending.equals(OWL.THING)) {
                            LOG.debug("Class '"
                                    + (iri != null ? iri.stringValue()
                                            : getOntologyName(this.packageName, this.ontologyName))
                                    + "' extends '" + extending + "'");
                            if (interfaces.containsKey(extending)) {
                                clazz._implements(interfaces.get(extending));
                            } else {
                                referenceOntologies.forEach(refOnt -> {
                                    if (refOnt.containsClass(extending)) {
                                        if (refOnt.getSourceGenerator() == null) {
                                            try {
                                                refOnt.generateSource(referenceOntologies);
                                            } catch (Exception e) {
                                                issues.add("Problem generating referenced data: " + e.getMessage());
                                            }
                                        }
                                        clazz._implements(refOnt.getSourceGenerator().getCodeModel()
                                                ._getClass(refOnt.getClassName(extending)));
                                        this.classMethodIriMap
                                                .putAll(refOnt.getSourceGenerator().getClassMethodIriMap());
                                    }
                                });
                            }
                        }
                    } else if (value instanceof org.openrdf.model.Resource) {
                        // TODO - handle blank nodes somehow
                    } else {
                        issues.add("Unsupported rdfs:subclassOf property on '" + iri.stringValue()
                                + "' which tried to extend '" + value.stringValue() + "'");
                    }
                });
            }
        });
        if (!issues.isEmpty()) {
            throw new OntologyToJavaException(
                    "Could not generate POJOs from ontology due to the following issues:\n\t"
                            + StringUtils.join(issues, "\n\t") + "\n\n");
        }
    }

    /**
     * This method will take a root IRI identifying a class, and then populate a
     * list with the complete ancestry of that class.
     *
     * @param root    The IRI to climb the ancestry tree of
     * @param parents The parent entities
     */
    private void getAncestors(final IRI root, final List<IRI> parents) {
        List<IRI> firstLevel = this.model.filter(root, RDFS.SUBCLASSOF, null).stream().map(stmt -> stmt.getObject())
                .filter(obj -> obj instanceof IRI).map(obj -> (IRI) obj).filter(iri -> !parents.contains(iri))
                .collect(Collectors.toList());
        parents.addAll(firstLevel);
        firstLevel.forEach(newRoot -> {
            getAncestors(newRoot, parents);
        });
    }

    private JDefinedClass generateOntologyThing() throws OntologyToJavaException {
        try {
            final JDefinedClass ontologyThing = codeModel._class(JMod.PUBLIC,
                    getOntologyName(this.packageName, this.ontologyName), ClassType.INTERFACE);
            ontologyThing._extends(codeModel.ref(Thing.class));
            // Track field names to the IRI.
            final Map<String, IRI> fieldIriMap = new HashMap<>();
            final Map<String, IRI> rangeMap = new HashMap<>();
            identifyAllDomainProperties(metaModel).stream().filter(resource -> resource instanceof IRI)
                    .forEach(resource -> {
                        // Set a static final field for the IRI.
                        final String fieldName = getName(false, (IRI) resource, this.metaModel);
                        final IRI range = getRangeOfProperty((IRI) resource);
                        ontologyThing
                                .field(JMod.PUBLIC | JMod.STATIC | JMod.FINAL, String.class, fieldName + "_IRI",
                                        JExpr.lit(resource.stringValue()))
                                .javadoc()
                                .add("IRI of the predicate that this property will represent.<br><br>Domain: "
                                        + range);
                        fieldIriMap.put(fieldName + "_IRI", (IRI) resource);
                        rangeMap.put(fieldName, range);
                    });
            interfaceFieldIriMap.put(ontologyThing, fieldIriMap);
            interfaceFieldRangeMap.put(ontologyThing, rangeMap);
            //TODO, null, or create some kind of unique IRI?
            interfaces.put(null, ontologyThing);
            return ontologyThing;
        } catch (JClassAlreadyExistsException e) {
            throw new OntologyToJavaException(
                    "Ontology Super Thing class already exists, or conflicts with an existing class...", e);
        }
    }

    /**
     * Generate each individual interface with its static final predicate
     * fields.
     *
     * @throws OntologyToJavaException
     */
    private void generateIndividualInterfaces() throws OntologyToJavaException {
        final List<String> issues = new ArrayList<>();

        final JDefinedClass ontologyThing = generateOntologyThing();

        identifyClasses().forEach(classIri -> {
            try {
                final Model modelOfThisClass = this.model.filter(classIri, null, null);

                final String className = packageName + "." + getName(true, classIri, modelOfThisClass);

                List<IRI> ancestors = new ArrayList<>();
                getAncestors(classIri, ancestors);

                JDefinedClass clazz = codeModel._class(JMod.PUBLIC, className, ClassType.INTERFACE);
                if (!clazz.equals(ontologyThing)) {
                    clazz._extends(ontologyThing);
                }

                clazz.javadoc().add("Generated class representing things with the type: " + classIri.stringValue());

                clazz.field(JMod.PUBLIC | JMod.STATIC | JMod.FINAL, String.class, CLASS_TYPE_IRI_FIELD,
                        JExpr.lit(classIri.stringValue())).javadoc().add("The rdf:type IRI of this class.");

                // Track field names to the IRI.
                final Map<String, IRI> fieldIriMap = new HashMap<>();
                final Map<String, IRI> rangeMap = new HashMap<>();

                // Look for properties on this domain.
                final Collection<Resource> resources = this.model.filter(null, RDFS.DOMAIN, classIri).subjects()
                        .stream().filter(subj -> {
                            for (final IRI ancestorIri : ancestors) {
                                if (this.model.filter(subj, RDFS.DOMAIN, ancestorIri).size() > 0) {
                                    return false;
                                }
                            }
                            return true;
                        }).collect(Collectors.toSet());
                resources.stream().filter(resource -> resource instanceof IRI).forEach(resource -> {
                    LOG.debug("Adding '" + resource.stringValue() + "' to '" + classIri.stringValue()
                            + "' as it specifies it in its range");

                    // Set a static final field for the IRI.
                    final String fieldName = getName(false, (IRI) resource, this.model);
                    final IRI range = getRangeOfProperty((IRI) resource);
                    clazz.field(JMod.PUBLIC | JMod.STATIC | JMod.FINAL, String.class, fieldName + "_IRI",
                            JExpr.lit(resource.stringValue())).javadoc()
                            .add("IRI of the predicate that this property will represent.<br><br>Domain: " + range);
                    fieldIriMap.put(fieldName + "_IRI", (IRI) resource);
                    rangeMap.put(fieldName, range);
                });
                interfaceFieldIriMap.put(clazz, fieldIriMap);
                interfaceFieldRangeMap.put(clazz, rangeMap);
                interfaces.put(classIri, clazz);
            } catch (Exception e) {
                issues.add("Issue generating interface for '" + classIri.stringValue() + "': " + e.getMessage());
            }
        });

        if (!issues.isEmpty()) {
            throw new OntologyToJavaException(
                    "Could not generate POJOs from ontology due to the following issues:\n\t"
                            + StringUtils.join(issues, "\n\t") + "\n\n");
        }

    }

    /**
     * Gets the range of a given property IRI.
     *
     * @param propertyIri The property to fetch the range of
     * @return The IRI representing the range of the property
     */
    private IRI getRangeOfProperty(final IRI propertyIri) {
        final Model submodel = this.model.filter(propertyIri, RDFS.RANGE, null);
        if (!submodel.isEmpty()) {
            return (IRI) submodel.iterator().next().getObject();
        } else {
            return null;
        }
    }

    /**
     * Determines whether or not a supplied property IRI is functional.
     *
     * @param propertyIri The {@link IRI} of the property to check
     * @return Whether or not the {@link IRI} is the subject in a statement
     * saying it is a owl:FunctionalProperty.
     */
    private boolean isPropertyFunctional(final IRI propertyIri) {
        return !this.model.filter(propertyIri, RDF.TYPE, OWL.FUNCTIONALPROPERTY).isEmpty();
    }

    /**
     * Identify all of the subjects in the ontology that have the rdf:type of
     * owl:Class.
     *
     * @return The {@link Collection} of {@link IRI}s that are classes in the
     * ontology
     */
    private Collection<IRI> identifyClasses() {
        final Model submodel = this.model.filter(null, RDF.TYPE, OWL.CLASS);
        classIris = new ArrayList<>(submodel.size());
        submodel.stream().filter(statement -> statement.getSubject() instanceof IRI)
                .forEach(statement -> classIris.add((IRI) statement.getSubject()));
        return classIris;
    }

    private String getFieldComment(final IRI propertyIri) {
        final StringBuilder builder = new StringBuilder();
        final Model propertyStuff = model.filter(propertyIri, null, null);
        propertyStuff.filter(propertyIri, RDFS.COMMENT, null).forEach(stmt -> {
            builder.append(stmt.getObject().stringValue());
            builder.append("\n\n");
        });
        return builder.toString().trim();
    }

    private JBlock convertValueBody(final JDefinedClass interfaceClass, final JMethod interfaceMethod,
            final JDefinedClass implClass, final JMethod implMethod) {
        final boolean returnsSet = interfaceMethod.type().fullName().startsWith("java.util.Set");

        JType returnType = (returnsSet) ? codeModel.ref(Set.class).narrow(org.matonto.rdf.api.Value.class)
                : codeModel.ref(Optional.class).narrow(org.matonto.rdf.api.Value.class);

        JExpression callGet = classMethodIriMap.containsKey(interfaceClass)
                ? JExpr.invoke(returnsSet ? "getProperties" : "getProperty")
                        .arg(JExpr.ref("valueFactory").invoke("createIRI")
                                .arg(interfaceClass
                                        .staticRef(classMethodIriMap.get(interfaceClass).get(interfaceMethod))))
                : null;

        final JVar value = implMethod.body().decl(JMod.FINAL, returnType, "value", callGet);

        boolean returnsValue = interfaceMethod.type()
                .equals(codeModel.ref(Set.class).narrow(org.matonto.rdf.api.Value.class));
        boolean returnsValueSet = (returnsSet && ((JClass) interfaceMethod.type()).getTypeParameters().get(0)
                .equals(codeModel.ref(org.matonto.rdf.api.Value.class)));

        if (returnsValue || returnsValueSet) {
            implMethod.body()._return(value);
        } else if (returnsSet) {
            implMethod.body()
                    ._return(JExpr.ref("valueConverterRegistry").invoke("convertValues").arg(value)
                            .arg(JExpr._this())
                            .arg(((JClass) interfaceMethod.type()).getTypeParameters().get(0).dotclass()));
        } else {
            JExpression convertValue = JExpr.ref("valueConverterRegistry").invoke("convertValue")
                    .arg(JExpr.ref("value").invoke("get")).arg(JExpr._this())
                    .arg((((JClass) interfaceMethod.type()).getTypeParameters().get(0)).dotclass());

            JConditional conditional = implMethod.body()._if(JExpr.ref("value").invoke("isPresent"));
            conditional._then()._return(codeModel.ref(Optional.class).staticInvoke("of").arg(convertValue));
            conditional._else()._return(codeModel.ref(Optional.class).staticInvoke("empty"));
        }

        return implMethod.body();
    }

    private IRI iriFromInterface(JDefinedClass interfaze) {
        for (final Map.Entry<IRI, JDefinedClass> entry : interfaces.entrySet()) {
            if (entry.getValue().equals(interfaze)) {
                return entry.getKey();
            }
        }
        return null;
    }

    private static Set<Resource> identifyAllDomainProperties(final Model metaModel) {
        final Set<Resource> resources = fetchAllDomainResources(OWL.OBJECTPROPERTY, metaModel)
                .collect(Collectors.toSet());
        resources.addAll(fetchAllDomainResources(OWL.DATATYPEPROPERTY, metaModel).collect(Collectors.toSet()));
        resources.addAll(fetchAllDomainResources(RDF.PROPERTY, metaModel).collect(Collectors.toSet()));
        return resources;
    }

    private static Stream<Resource> fetchAllDomainResources(final IRI typeIri, final Model metaModel) {
        return metaModel.filter(null, RDF.TYPE, typeIri).subjects().stream()
                .filter(resource -> metaModel.filter(resource, RDFS.DOMAIN, null).isEmpty());
    }

    public JCodeModel getCodeModel() {
        return codeModel;
    }

    public Map<JDefinedClass, JDefinedClass> getInterfaceImplMap() {
        return interfaceImplMap;
    }

    public Map<JDefinedClass, Map<String, IRI>> getInterfaceFieldIriMap() {
        return interfaceFieldIriMap;
    }

    public Map<JDefinedClass, Map<JMethod, JFieldVar>> getClassMethodIriMap() {
        return classMethodIriMap;
    }

    public Map<JDefinedClass, Map<String, IRI>> getInterfaceFieldRangeMap() {
        return interfaceFieldRangeMap;
    }

    public Map<IRI, JDefinedClass> getInterfaces() {
        return interfaces;
    }

    protected static String getOntologyName(final String packageName, final String ontologyName) {
        return packageName + "." + (StringUtils.isBlank(ontologyName) ? "" : stripWhiteSpace(ontologyName))
                + "_Thing";
    }
}