public class Ontology {
    
    /**
     * The id of the ontology.
     */
    protected final String id;
    
    /**
     * The version of the ontology.
     */
    protected final String version;
    
    /**
     * The collection of stated axioms that form the ontology.
     */
    protected final Collection<Axiom> statedAxioms;
    
    /**
     * The collection of inferred axioms that form the ontology.
     */
    protected final Collection<Axiom> inferredAxioms = new ArrayList<Axiom>();
    
    /**
     * A map that contains references to all the nodes in the taxonomy indexed
     * by id.
     */
    protected final Map<String, Node> nodeMap = new HashMap<String, Node>();
    
    /**
     * Set of {@link Node}s potentially affected by the last incremental
     * classification.
     */
    protected final Set<Node> lastAffectedNodes = new HashSet<Node>();
    
    /**
     * Builds a new ontology.
     * 
     * @param id
     * @param version
     * @param statedAxioms
     * @param nodeMap
     * @param lastAffectedNodes
     */
    public Ontology(String id, String version, Collection<Axiom> statedAxioms, Map<String, Node> nodeMap, 
            Set<Node> lastAffectedNodes) {
        this.id = id;
        this.version = version;
        if(statedAxioms == null) {
            this.statedAxioms = new ArrayList<Axiom>();
        } else {
            this.statedAxioms = statedAxioms;
        }
        if(nodeMap != null)
            this.nodeMap.putAll(nodeMap);
        if(lastAffectedNodes != null)
            this.lastAffectedNodes.addAll(lastAffectedNodes);
    }
    
    /**
     * Builds a new ontology.
     * 
     * @param statedAxioms
     * @param nodeMap
     */
    public Ontology(String id, String version, Collection<Axiom> statedAxioms, Map<String, Node> nodeMap) {
    	this(id, version, statedAxioms, nodeMap, null);
    }
    
    public Collection<Axiom> getStatedAxioms() {
        return statedAxioms;
    }

    public Collection<Axiom> getInferredAxioms() {
        return inferredAxioms;
    }
    
    public Node getNode(String id) {
        return nodeMap.get(id);
    }

    public Iterator<Node> nodeIterator() {
        Set<Node> set = new HashSet<Node>(nodeMap.values());
        return set.iterator();
    }
    
    public Map<String, Node> getNodeMap() {
        return nodeMap;
    }

    public Node getTopNode() {
        return getNode(NamedConcept.TOP);
    }
    
    public Node getBottomNode() {
        return getNode(NamedConcept.BOTTOM);
    }
    
    public void setNodeMap(Map<String, Node> nodeMap) {
        this.nodeMap.clear();
        this.nodeMap.putAll(nodeMap);
    }

    public Set<Node> getAffectedNodes() {
        return lastAffectedNodes;
    }
    
    public void setAffectedNodes(Set<Node> nodes) {
        lastAffectedNodes.clear();
        lastAffectedNodes.addAll(nodes);
    }
    
    public String getId() {
        return id;
    }

    public String getVersion() {
        return version;
    }
    
} public abstract class BaseImporter implements IImporter {
    
    protected class Module {
        private final String moduleId;
        private final String moduleVersion;
        
        public Module(String moduleId, String moduleVersion) {
            super();
            this.moduleId = moduleId;
            this.moduleVersion = moduleVersion;
        }
        
        public String getModuleId() {
            return moduleId;
        }
        
        public String getModuleVersion() {
            return moduleVersion;
        }
        
    }
    
    protected class ImportEntry {
        private final String rootModuleId;
        private final String rootModuleVersion;
        private final Map<String, String> metadata;
        private final Set<Module> modules;
        private final Input input;
        
        public ImportEntry(String rootModuleId, String rootModuleVersion,
                Map<String, String> metadata, Set<Module> modules, Input input) {
            this.rootModuleId = rootModuleId;
            this.rootModuleVersion = rootModuleVersion;
            this.metadata = metadata;
            this.modules = modules;
            this.input = input;
        }
        
        public String getRootModuleId() {
            return rootModuleId;
        }
        
        public String getRootModuleVersion() {
            return rootModuleVersion;
        }
        
        public Map<String, String> getMetadata() {
            return metadata;
        }
        
        public Set<Module> getModules() {
            return modules;
        }

        public Input getInput() {
            return input;
        }
        
    }
    
} public class ImportException extends Exception {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    /**
     * @param arg0
     */
    public ImportException(String arg0) {
        super(arg0);
    }

    /**
     * @param arg0
     * @param arg1
     */
    public ImportException(String arg0, Throwable arg1) {
        super(arg0, arg1);
    }

} public class OWLImporter extends BaseImporter {

    public static final String THING_IRI = "http://www.w3.org/2002/07/owl#Thing";
    public static final String NOTHING_IRI = "http://www.w3.org/2002/07/owl#Nothing";

    private final Set<OWLDataPropertyRangeAxiom> dprAxioms = new HashSet<>();
    private final List<String> problems = new ArrayList<>();

    private OWLOntology ontology;
    private List<OWLAxiom> axioms;

    private SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");

    /**
     * The OWL EL spec only allows using the following types: owl:real, owl:rational, xsd:decimal, xsd:integer,
     * xsd:nonNegativeInteger. The Java types that most naturally correspond to these types are BigInteger and
     * BigDecimal. However, the memory consumption increases and the performance decreases when using these types
     * instead of the Java built-in types. When the following flags are set to true, the transformation uses the
     * built-in data types instead of the math types.
     */
    private boolean useSimpleInts = true;
    private boolean useSimpleFloats = false;

    public OWLImporter(OWLOntology ontology) {
        this();
        this.ontology = ontology;
    }

    public OWLImporter(List<OWLAxiom> axioms) {
        this();
        this.axioms = axioms;
    }

    /**
     * Private constructor.
     */
    private OWLImporter() {

    }

    /**
     * @return the useSimpleInts
     */
    public boolean isUseSimpleInts() {
        return useSimpleInts;
    }

    /**
     * @param useSimpleInts the useSimpleInts to set
     */
    public void setUseSimpleInts(boolean useSimpleInts) {
        this.useSimpleInts = useSimpleInts;
    }

    /**
     * @return the useSimpleFloats
     */
    public boolean isUseSimpleFloats() {
        return useSimpleFloats;
    }

    /**
     * @param useSimpleFloats the useSimpleFloats to set
     */
    public void setUseSimpleFloats(boolean useSimpleFloats) {
        this.useSimpleFloats = useSimpleFloats;
    }

    private Collection<Axiom> transformOWLSubPropertyChainOfAxiom(OWLSubPropertyChainOfAxiom a) {
        List<OWLObjectPropertyExpression> sub = a.getPropertyChain();
        OWLObjectPropertyExpression sup = a.getSuperProperty();

        int size = sub.size();
        NamedRole[] lhss = new NamedRole[size];
        for (int i = 0; i < size; i++) {
            lhss[i] = new NamedRole(sub.get(i).asOWLObjectProperty().toStringID());
        }

        NamedRole rhs = new NamedRole(sup.asOWLObjectProperty().toStringID());

        if (lhss.length == 1 || lhss.length == 2) {
            return Arrays.asList(new RoleInclusion(lhss, rhs), new ConceptInclusion(new NamedConcept(lhss[0].getId()), new NamedConcept(rhs.getId())));
        } else {
            problems.add("Unable to import axiom "+a.toString()+". RoleChains longer than 2 not supported.");
            return null;
        }
    }

    private Collection<Axiom> transformOWLSubObjectPropertyOfAxiom(OWLSubObjectPropertyOfAxiom a) {
        OWLObjectPropertyExpression sub = a.getSubProperty();
        OWLObjectPropertyExpression sup = a.getSuperProperty();

        NamedRole lhs = new NamedRole(sub.asOWLObjectProperty().toStringID());
        NamedRole rhs = new NamedRole(sup.asOWLObjectProperty().toStringID());

        return Arrays.asList(new RoleInclusion(new Role[]{lhs}, rhs), new ConceptInclusion(new NamedConcept(lhs.getId()), new NamedConcept(rhs.getId())));
    }

    private Axiom transformOWLReflexiveObjectPropertyAxiom(OWLReflexiveObjectPropertyAxiom a) {
        OWLObjectPropertyExpression exp = a.getProperty();
        return new RoleInclusion(new Role[] {}, new NamedRole(exp.asOWLObjectProperty().toStringID()));
    }

    private Axiom transformOWLTransitiveObjectPropertyAxiom(OWLTransitiveObjectPropertyAxiom a) {
        OWLObjectPropertyExpression exp = a.getProperty();
        Role r = new NamedRole(exp.asOWLObjectProperty().toStringID());
        return new RoleInclusion(new Role[] { r, r }, r);
    }

    private Axiom transformOWLFunctionalDataPropertyAxiom(OWLFunctionalDataPropertyAxiom a) {
        OWLDataPropertyExpression prop = a.getProperty();
        NamedFeature feature = new NamedFeature(prop.asOWLDataProperty().toStringID());
        return new FunctionalFeature(feature);
    }

    private Collection<Axiom> transformOWLSubDataPropertyOfAxiom(OWLSubDataPropertyOfAxiom a) {
        // special-case SNOMED parent concept (762706009 is not a real data property)
        if (!"762706009".equals(a.getSuperProperty().asOWLDataProperty().toStringID())) {
            final String message = "The axiom " + a.toString() + " is not currently supported by Snorocket.";
            problems.add(message);
            System.err.println(message);
            throw new RuntimeException(message);
        }

        // Define the feature
        OWLDataPropertyExpression prop = a.getSubProperty();
        NamedFeature feature = new NamedFeature(prop.asOWLDataProperty().toStringID());

        // Define the corresponding "concept"
        OWLDataProperty lhs = a.getSubProperty().asOWLDataProperty();
        OWLDataProperty rhs = a.getSuperProperty().asOWLDataProperty();
        ConceptInclusion ci = new ConceptInclusion(new NamedConcept(lhs.toStringID()), new NamedConcept(rhs.toStringID()));

        return Arrays.asList(new FunctionalFeature(feature), ci);
    }

    private Axiom transformOWLSubClassOfAxiom(OWLSubClassOfAxiom a) {
        OWLClassExpression sub = a.getSubClass();
        OWLClassExpression sup = a.getSuperClass();

        try {
            Concept subConcept = getConcept(sub);
            Concept superConcept = getConcept(sup);
            return new ConceptInclusion(subConcept, superConcept);
        } catch(UnsupportedOperationException e) {
            problems.add(e.getMessage());
            return null;
        }
    }

    private List<Axiom> transformOWLEquivalentClassesAxiom(
            OWLEquivalentClassesAxiom a) {
        List<Axiom> axioms = new ArrayList<>();
        List<OWLClassExpression> exps = a.getClassExpressionsAsList();

        int size = exps.size();

        for (int i = 0; i < size - 1; i++) {
            try {
                OWLClassExpression e1 = exps.get(i);
                Concept concept1 = getConcept(e1);
                for (int j = i; j < size; j++) {
                    OWLClassExpression e2 = exps.get(j);
                    if (e1 == e2)
                        continue;
                    Concept concept2 = getConcept(e2);
                    axioms.add(new ConceptInclusion(concept1, concept2));
                    axioms.add(new ConceptInclusion(concept2, concept1));
                }
            } catch(UnsupportedOperationException e) {
                problems.add(e.getMessage());
            }
        }
        return axioms;
    }

    private Axiom transformOWLDisjointClassesAxiom(OWLDisjointClassesAxiom a) {
        try {
            List<OWLClassExpression> exps = a.getClassExpressionsAsList();
            List<Concept> concepts = new ArrayList<>();
            for (OWLClassExpression exp : exps) {
                concepts.add(getConcept(exp));
            }

            Concept[] conjs = new Concept[concepts.size()];
            int i = 0;
            for (; i < concepts.size(); i++) {
                conjs[i] = concepts.get(i);
            }

            return new ConceptInclusion(new Conjunction(conjs), NamedConcept.BOTTOM_CONCEPT);
        } catch(UnsupportedOperationException e) {
            problems.add(e.getMessage());
            return null;
        }
    }

    private List<Axiom> transformOWLEquivalentObjectPropertiesAxiom(
            OWLEquivalentObjectPropertiesAxiom a) {
        List<Axiom> axioms = new ArrayList<>();
        for (OWLSubObjectPropertyOfAxiom ax : a.asSubObjectPropertyOfAxioms()) {
            OWLObjectPropertyExpression sub = ax.getSubProperty();
            OWLObjectPropertyExpression sup = ax.getSuperProperty();

            axioms.add(
                    new RoleInclusion(new NamedRole(sub.asOWLObjectProperty().toStringID()),
                    new NamedRole(sup.asOWLObjectProperty().toStringID()))
            );
        }
        return axioms;
    }

    private Set<Axiom> transform(List<OWLAxiom> axioms, IProgressMonitor monitor) throws ImportException {
        monitor.taskStarted("Loading axioms");
        final Set<Axiom> res = new HashSet<>();
        int totalAxioms = axioms.size();
        int workDone = 0;

        for (OWLAxiom axiom : axioms) {
            if (axiom instanceof OWLDeclarationAxiom) {
                OWLDeclarationAxiom a = (OWLDeclarationAxiom)axiom;
                OWLEntity ent = a.getEntity();
                if (ent.isOWLClass()) {
                    res.add(new ConceptInclusion(
                            new NamedConcept(ent.asOWLClass().toStringID()), NamedConcept.TOP_CONCEPT));
                } else if (ent.isOWLObjectProperty()) {
                    // Do nothing for now.
                } else if (ent.isOWLDataProperty()) {
                    // Do nothing for now.
                }
            } else if (axiom instanceof OWLSubPropertyChainOfAxiom) {
                OWLSubPropertyChainOfAxiom a = (OWLSubPropertyChainOfAxiom) axiom;
                Collection<Axiom> ax = transformOWLSubPropertyChainOfAxiom(a);
                if(ax != null) res.addAll(ax);
                monitor.step(workDone, totalAxioms);
            } else if (axiom instanceof OWLSubObjectPropertyOfAxiom) {
                OWLSubObjectPropertyOfAxiom a = (OWLSubObjectPropertyOfAxiom) axiom;
                res.addAll(transformOWLSubObjectPropertyOfAxiom(a));
                monitor.step(++workDone, totalAxioms);
            } else if (axiom instanceof OWLReflexiveObjectPropertyAxiom) {
                OWLReflexiveObjectPropertyAxiom a = (OWLReflexiveObjectPropertyAxiom) axiom;
                res.add(transformOWLReflexiveObjectPropertyAxiom(a));
                monitor.step(++workDone, totalAxioms);
            } else if (axiom instanceof OWLTransitiveObjectPropertyAxiom) {
                OWLTransitiveObjectPropertyAxiom a = (OWLTransitiveObjectPropertyAxiom) axiom;
                res.add(transformOWLTransitiveObjectPropertyAxiom(a));
                monitor.step(++workDone, totalAxioms);
            } else if (axiom instanceof OWLSubClassOfAxiom) {
                OWLSubClassOfAxiom a = (OWLSubClassOfAxiom) axiom;
                Axiom ax = transformOWLSubClassOfAxiom(a);
                if(ax != null) res.add(ax);
                monitor.step(++workDone, totalAxioms);
            } else if (axiom instanceof OWLEquivalentClassesAxiom) {
                OWLEquivalentClassesAxiom a = (OWLEquivalentClassesAxiom) axiom;
                res.addAll(transformOWLEquivalentClassesAxiom(a));
                monitor.step(++workDone, totalAxioms);
            } else if (axiom instanceof OWLDisjointClassesAxiom) {
                OWLDisjointClassesAxiom a = (OWLDisjointClassesAxiom) axiom;
                Axiom ax = transformOWLDisjointClassesAxiom(a);
                if(ax != null) res.add(ax);
                monitor.step(++workDone, totalAxioms);
            } else if (axiom instanceof OWLEquivalentObjectPropertiesAxiom) {
                OWLEquivalentObjectPropertiesAxiom a = (OWLEquivalentObjectPropertiesAxiom) axiom;
                res.addAll(transformOWLEquivalentObjectPropertiesAxiom(a));
                monitor.step(++workDone, totalAxioms);
            } else if (axiom instanceof OWLFunctionalDataPropertyAxiom) {
                OWLFunctionalDataPropertyAxiom a = (OWLFunctionalDataPropertyAxiom) axiom;
                res.add(transformOWLFunctionalDataPropertyAxiom(a));
                monitor.step(++workDone, totalAxioms);
            } else if (axiom instanceof OWLSubDataPropertyOfAxiom) {
                OWLSubDataPropertyOfAxiom a = (OWLSubDataPropertyOfAxiom) axiom;
                res.addAll(transformOWLSubDataPropertyOfAxiom(a));
                monitor.step(++workDone, totalAxioms);
            } else if (axiom instanceof OWLAnnotationAssertionAxiom) {
                // Do nothing
                monitor.step(++workDone, totalAxioms);
            } else {
                problems.add("The axiom " + axiom.toString() + " is not currently supported by Snorocket.");
                System.err.println("The axiom " + axiom.toString() + " is not currently supported by Snorocket.");
            }
        }

        // TODO: deal with other axioms types even if Snorocket does not
        // currently support them

        monitor.taskEnded();

        /*
        if(!problems.isEmpty()) {
            throw new ImportException("Problems occurred during import. See getProblems()");
        }
        */

        return res;
    }

	private Set<Axiom> transform(OWLOntology ont, IProgressMonitor monitor) throws ImportException {
        return transform(Collections.list(Collections.enumeration(ont.getAxioms())), monitor);
    }

    /**
     *
     * @param l
     * @return
     */
    private Literal getLiteral(OWLLiteral l) {
        OWLDatatype dt = l.getDatatype();
        String literal = l.getLiteral();

        Literal res = null;

        if(dt.isBuiltIn()) {
            OWL2Datatype odt = dt.getBuiltInDatatype();
            switch (odt) {
                case RDF_PLAIN_LITERAL:
                case RDF_XML_LITERAL:
                case XSD_STRING:
                case XSD_NORMALIZED_STRING:
                case XSD_NAME:
                case XSD_NCNAME:
                case XSD_NMTOKEN:
                case XSD_HEX_BINARY:
                case XSD_BASE_64_BINARY:
                case XSD_ANY_URI:
                case XSD_TOKEN:
                    res = new StringLiteral(literal);
                    break;
                case XSD_INTEGER:
                case XSD_NON_NEGATIVE_INTEGER:
                    if(useSimpleInts) {
                        res = new IntegerLiteral(Integer.parseInt(literal));
                    } else {
                        res = new BigIntegerLiteral(new BigInteger(literal));
                    }
                    break;
                case XSD_DATE_TIME:
                    res = new DateLiteral(DatatypeConverter.parseDateTime(literal));
                    break;
                case OWL_RATIONAL:
                case OWL_REAL:
                case XSD_DECIMAL:
                    if(useSimpleFloats) {
                        res = new FloatLiteral(Float.parseFloat(literal));
                    } else {
                        res = new DecimalLiteral(new BigDecimal(literal));
                    }
                    break;
                default:
                    problems.add("Unsupported literal " + l);
            }
        } else {
            problems.add("Datatype is not built in: " + dt);
        }

        return res;
    }

    private void checkInconsistentProperty(OWLDataProperty dp, OWLDatatype type) {
        for (OWLDataPropertyRangeAxiom a : dprAxioms) {
            OWLDataPropertyExpression pe = a.getProperty();
            OWLDataRange r = a.getRange();
            // TODO: check DataOneOf
            // TODO: check OWLDataIntersectionOf
            OWLDatatype otype = r.asOWLDatatype();

            if (!pe.isAnonymous()) {
                if (!otype.equals(type)) {
                    problems.add("The literal value restriction " + dp + " is inconsistent with the data property " +
                    		"range axiom " + a);
                }
            } else {
                problems.add("Found anonymous data property expression in data property range axiom: " + pe);
            }
        }
    }

    /**
     *
     * @param desc
     * @return
     */
    private Concept getConcept(OWLClassExpression desc) {
        final Stack<Concept> stack = new Stack<>();
        desc.accept(new OWLClassExpressionVisitor() {

            private void unimplemented(OWLClassExpression e) {
                String message = "The class expression "+ e.getClassExpressionType().getName()+
                        " is not currently supported by Snorocket.";
                throw new UnsupportedOperationException(message);
            }

            private Concept pop() {
                return stack.pop();
            }

            private void push(Concept concept) {
                stack.push(concept);
            }

            @Override
            public void visit(OWLDataMaxCardinality e) {
                unimplemented(e);
            }

            @Override
            public void visit(OWLDataExactCardinality e) {
                unimplemented(e);
            }

            @Override
            public void visit(OWLDataMinCardinality e) {
                unimplemented(e);
            }

            @Override
            public void visit(OWLDataHasValue e) {
                OWLDataPropertyExpression dpe = e.getProperty();
                // TODO: consider the case where dpe is anonymous
                OWLDataProperty dp = dpe.asOWLDataProperty();
                OWLLiteral l = e.getFiller();
                OWLDatatype type = l.getDatatype();

                checkInconsistentProperty(dp, type);

                NamedFeature f = new NamedFeature(dp.toStringID());
                Literal lit = getLiteral(l);
                if(lit != null) {
                    push(new Datatype(f, Operator.EQUALS,lit));
                } else {
                    problems.add("Axiom " + e + " will be ignored because of the unsupported literal.");
                }
            }

            @Override
            public void visit(OWLDataAllValuesFrom e) {
                unimplemented(e);
            }

            @Override
            public void visit(OWLDataSomeValuesFrom e) {
                OWLDataProperty dp = e.getProperty().asOWLDataProperty();
                OWLDataRange range = e.getFiller();

                /*
                 * An OWLDataRange can be one of the following:
                 * Datatype | DataIntersectionOf | DataUnionOf |
                 * DataComplementOf | DataOneOf | DatatypeRestriction
                 *
                 * We initially support only DataOneOf.
                 */
                if(range instanceof OWLDataOneOf) {
                    OWLDataOneOf doo = (OWLDataOneOf)range;
                    Set<OWLLiteral> values = doo.getValues();
                    if(values.size() != 1) {
                        problems.add("Expected only a single literal in "+e);
                        return;
                    }
                    OWLLiteral l = (OWLLiteral)values.toArray()[0];
                    OWLDatatype type = l.getDatatype();
                    checkInconsistentProperty(dp, type);

                    NamedFeature f = new NamedFeature(dp.toStringID());
                    Literal lit = getLiteral(l);
                    if(lit != null) {
                        push(new Datatype(f, Operator.EQUALS, lit));
                    } else {
                        problems.add("Axiom " + e + " will be ignored because of the unsupported literal.");
                    }
                } else if(range instanceof OWLDatatypeRestriction) {
                    NamedFeature f = new NamedFeature(dp.toStringID());

                    OWLDatatypeRestriction dtr = (OWLDatatypeRestriction)range;
                    Set<OWLFacetRestriction> frs = dtr.getFacetRestrictions();

                    List<Datatype> conjuncts = new ArrayList<>();
                    for(OWLFacetRestriction fr : frs) {
                        OWLLiteral l = fr.getFacetValue();

                        Literal lit = getLiteral(l);

                        if(lit != null) {
                            checkInconsistentProperty(dp, l.getDatatype());
                            OWLFacet facet = fr.getFacet();

                            switch(facet) {
                                case MAX_EXCLUSIVE:
                                    conjuncts.add(new Datatype(f, Operator.LESS_THAN, lit));
                                    break;
                                case MAX_INCLUSIVE:
                                    conjuncts.add(new Datatype(f, Operator.LESS_THAN_EQUALS, lit));
                                    break;
                                case MIN_EXCLUSIVE:
                                    conjuncts.add(new Datatype(f, Operator.GREATER_THAN, lit));
                                    break;
                                case MIN_INCLUSIVE:
                                    conjuncts.add(new Datatype(f, Operator.GREATER_THAN_EQUALS, lit));
                                    break;
                                default:
                                    throw new RuntimeException("Unsupported facet "+facet);
                            }
                        } else {
                            problems.add("Axiom " + e + " will be ignored because of the unsupported literal.");
                        }
                    }

                    // Create conjunctions with all restrictions
                    if(conjuncts.size() == 1) {
                        push(conjuncts.get(0));
                    } else if(!conjuncts.isEmpty()){
                        push(new Conjunction(conjuncts));
                    }
                } else {
                    throw new RuntimeException("Unsupporter OWLDataRange: "+
                            range.getClass().getName());
                }
            }

            @Override
            public void visit(OWLObjectOneOf e) {
                // TODO: implement to support EL profile
                unimplemented(e);
            }

            @Override
            public void visit(OWLObjectHasSelf e) {
                // TODO: implement to support EL profile

                // There is no model object to support this.

                /*
                 * A self-restriction ObjectHasSelf( OPE ) consists of an object
                 * property expression OPE, and it contains all those
                 * individuals that are connected by OPE to themselves.
                 */

                /*Role r = new Role<>(e.getProperty().asOWLObjectProperty().toStringID());*/

                unimplemented(e);
            }

            @Override
            public void visit(OWLObjectMaxCardinality e) {
                unimplemented(e);
            }

            @Override
            public void visit(OWLObjectExactCardinality e) {
                unimplemented(e);
            }

            @Override
            public void visit(OWLObjectMinCardinality e) {
                unimplemented(e);
            }

            @Override
            public void visit(OWLObjectHasValue e) {
                // TODO: implement to support EL profile

                // We do not support individuals
                unimplemented(e);
            }

            @Override
            public void visit(OWLObjectAllValuesFrom e) {
                unimplemented(e);
            }

            @Override
            public void visit(OWLObjectSomeValuesFrom e) {
                NamedRole r = new NamedRole(e.getProperty().asOWLObjectProperty().toStringID());
                e.getFiller().accept(this);
                try {
                    push(new Existential(r, pop()));
                } catch(EmptyStackException ex) {
                    problems.add("Unable to add axiom " + e + " because of previous problems.");
                }
            }

            @Override
            public void visit(OWLObjectComplementOf e) {
                unimplemented(e);
            }

            @Override
            public void visit(OWLObjectUnionOf e) {
                unimplemented(e);
            }

            @Override
            public void visit(OWLObjectIntersectionOf e) {
                List<Concept> items = new ArrayList<>();

                for (OWLClassExpression desc : e.getOperands()) {
                    desc.accept(this);
                    try {
                        items.add(pop());
                    } catch(EmptyStackException ex) {
                        problems.add("Unable to add conjunct " + desc + " because of previous problems.");
                    }
                }

                if(!items.isEmpty()) {
                    Conjunction conj = new Conjunction(items);
                    push(conj);
                }
            }

            @Override
            public void visit(OWLClass e) {
                String id = e.toStringID();
                if (("<"+THING_IRI+">").equals(id) || THING_IRI.equals(id))
                    push(NamedConcept.TOP_CONCEPT);
                else if (("<"+NOTHING_IRI+">").equals(id) || NOTHING_IRI.equals(id))
                    push(NamedConcept.BOTTOM_CONCEPT);
                else
                    push(new NamedConcept(id));
            }

        });

        if (stack.size() != 1) {
            throw new RuntimeException("Stack size should be 1 but is " + stack.size());
        }

        return stack.pop();
    }

    /**
     * Clears all the state in the importer.
     */
    public void clear() {
        dprAxioms.clear();
        problems.clear();
    }

    /**
     * @return the problems
     */
    @Override
    public List<String> getProblems() {
        return problems;
    }

    @Override
    public Iterator<Ontology> getOntologyVersions(IProgressMonitor monitor) {
        return new OntologyIterator(monitor);
    }

    class OntologyIterator implements Iterator<Ontology> {
        private boolean accessed = false;
        private IProgressMonitor monitor;

        public OntologyIterator(IProgressMonitor monitor) {
            this.monitor = monitor;
        }

        @Override
        public boolean hasNext() {
            return !accessed;
        }

        @Override
        public Ontology next() throws IllegalArgumentException, RuntimeException {
            long start = System.currentTimeMillis();

            Set<Axiom> ont = null;
            try {
                if(ontology != null) {
                    ont = transform(ontology, monitor);
                } else if(axioms != null) {
                    ont = transform(axioms, monitor);
                } else {
                    throw new IllegalArgumentException("No OWL ontology to transform.");
                }
            } catch (ImportException e) {
                throw new RuntimeException(e);
            }

            String id = null;
            if(ontology != null) {
                id = ontology.getOntologyID().toString();
            } else {
                id = "incremental";
            }

            String version = sdf.format(new Date());

            Ontology res = new Ontology(id, version, ont, null);
            Statistics.INSTANCE.setTime("owl loading", System.currentTimeMillis() - start);
            accessed = true;
            return res;
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }

    }

} public abstract class Input {

    /**
     * Indicates if the files should be loaded from an external file system or
     * the class path.
     *
     * @author Alejandro Metke
     *
     */
    public enum InputType {
        EXTERNAL, CLASSPATH
    };

    protected InputType inputType;
    protected String base;

    protected ZipFile zip = null;

    /**
     * @return the type
     */
    public InputType getInputType() {
        return inputType;
    }

    /**
     * @param type the type to set
     */
    public void setInputType(InputType inputType) {
        this.inputType = inputType;
    }

    /**
     * Optional base for resolving names against when {@code Input#getInputType()} is {@code InputType#EXTERNAL}.
     * <p>
     * If it resolves to a File, then it is treated as a ZIP file, otherwise it is treated as a directory.
     * @return
     */
    public String getBase() {
        return base;
    }

    public void setBase(String base) {
        this.base = base;
    }

    public InputStream getInputStream(String name) throws IOException {
        final InputType type = getInputType();

        if (type.equals(InputType.EXTERNAL)) {
            File file;
            if (base != null) {
                File baseFile = new File(base);
                if (baseFile.isDirectory()) {
                    File child = new File(name);
                    if (child.isAbsolute()) {
                        file = child;                           // Absolute name
                    } else {
                        file = new File(baseFile, name);        // Relative name resolved against base
                    }
                } else {
                    zip = new ZipFile(base);                    // name resolved in Zip file
                    return zip.getInputStream(zip.getEntry(name));
                }
            } else {
                file = new File(name);                          // No base; default name resolution
            }

            return new FileInputStream(file);
        } else if (type.equals(InputType.CLASSPATH)) {
            final InputStream stream = this.getClass().getResourceAsStream(name);
            if (null == stream) {
                throw new FileNotFoundException(name + " not found in classpath");
            }
            return stream;
        } else {
            throw new RuntimeException("Unexpected input type " + type);
        }
    }

    /**
     * Release internal state.
     * <p>
     * Note that this will close any InputStreams linked to a base Zip file.
     */
    public void close() throws IOException {
        if (null != zip) {
            zip.close();
            zip = null;
        }
    }

    public void validate() {
        switch (getInputType()) {
        case CLASSPATH:
            if (null != getBase()) {
                throw new RuntimeException("Base must not be specified with " + getInputType());
            }
            break;
        case EXTERNAL:
            if (null == getBase()) {
                throw new RuntimeException("Base must be specified with " + getInputType());
            }
            break;
        default:
            throw new RuntimeException("Invalid inputType: " + getInputType());
        }
    }

} public enum InputType {
    EXTERNAL, CLASSPATH
}; @XmlRootElement
public class Inputs {

    /**
     * Indicates the type of SNOMED release.
     *
     * @author Alejandro Metke
     *
     */
    public enum ReleaseType {
        FULL, SNAPSHOT, INCREMENTAL
    };

    /**
     * List of input files.
     */
    protected List<Input> inputs = new ArrayList<Input>();

    /**
     * Loads an {@link Inputs} object from am XML file.
     *
     * @param file
     * @return
     * @throws JAXBException
     */
    public static Inputs load(InputStream in) throws JAXBException {
        JAXBContext context = JAXBContext.newInstance(Inputs.class, RF2Input.class, OWLInput.class);
        Unmarshaller u = context.createUnmarshaller();
        Inputs inputs = (Inputs) u.unmarshal(in);

        inputs.validate();

        return inputs;
    }

    public static RF2Input loadRF2(InputStream in) throws JAXBException {
        JAXBContext context = JAXBContext.newInstance(RF2Input.class, OWLInput.class);
        Unmarshaller u = context.createUnmarshaller();
        RF2Input input = (RF2Input) u.unmarshal(in);

        input.validate();

        return input;
    }


    /**
     * Constructor.
     *
     * @param configFile
     */
    public Inputs() {

    }

    public void validate() {
        inputs.stream().forEach(i -> i.validate());
    }

    /**
     * @return the inputs
     */
    public List<Input> getInputs() {
        return inputs;
    }

    /**
     * @param inputs the inputs to set
     */
    public void setInputs(List<Input> inputs) {
        this.inputs = inputs;
    }

} public class ModuleInfo {
    protected String id;
    protected List<Version> versions = new ArrayList<Version>();
    
    public ModuleInfo() {
        
    }
    
    public ModuleInfo(String id) {
        super();
        this.id = id;
    }

    /**
     * @return the id
     */
    public String getId() {
        return id;
    }

    /**
     * @return the versions
     */
    public List<Version> getVersions() {
        return versions;
    }

    /**
     * @param id the id to set
     */
    public void setId(String id) {
        this.id = id;
    }

    /**
     * @param versions the versions to set
     */
    public void setVersions(List<Version> versions) {
        this.versions = versions;
    }
    
} public class RF2Input extends Input {
    // Terminology files
    protected Set<String> conceptsFiles = new HashSet<>();
    protected Set<String> descriptionsFiles = new HashSet<>();
    protected Set<String> identifiersFiles = new HashSet<>();
    protected Set<String> relationshipsFiles = new HashSet<>();         // deprecated
    protected Set<String> statedRelationshipsFiles = new HashSet<>();
    protected Set<String> nnfRelationshipsFiles = new HashSet<>();
    protected Set<String> nnfConcreteDomainsFiles = new HashSet<>();
    protected Set<String> textDefinitionsFiles = new HashSet<>();
    protected Set<String> owlOntologyRefsetFiles = new HashSet<>();     // deprecated
    protected Set<String> owlAxiomRefsetFiles = new HashSet<>();        // deprecated
    protected Set<String> owlExpressionRefsetFiles = new HashSet<>();

    // Reference set files
    protected Set<String> refsetDescriptorRefsetFiles = new HashSet<>();
    protected Set<String> descriptionFormatRefsetFiles = new HashSet<>();
    protected Set<String> moduleDependenciesRefsetFiles = new HashSet<>();
    protected Set<String> languageRefsetFiles = new HashSet<>();
    protected Set<String> simpleRefsetFiles = new HashSet<>();
    protected Set<String> orderedRefsetFiles = new HashSet<>();
    protected Set<String> attributeValueRefsetFiles = new HashSet<>();
    protected Set<String> simpleMapRefsetFiles = new HashSet<>();
    protected Set<String> complexMapRefsetFiles = new HashSet<>();
    protected Set<String> querySpecificationRefsetFiles = new HashSet<>();
    protected Set<String> annotationRefsetFiles = new HashSet<>();
    protected Set<String> associationRefsetFiles = new HashSet<>();
    protected Set<String> concreteDomainRefsetFiles = new HashSet<>();
    protected Set<String> attributeDomainRefsetFiles = new HashSet<>();

    // The type of release - full, snapshot or incremental
    protected ReleaseType releaseType;

    // List of module information - indicates which modules should be
    // processed and includes the meta-data needed to do so
    protected List<ModuleInfo> modules = new ArrayList<>();

    /**
     * Constructor.
     */
    public RF2Input() {

    }

    @Override
    public void validate() {
        super.validate();

//        if (!relationshipsFiles.isEmpty()) {
//            throw new RuntimeException("relationshipsFiles is deprecated.");
//        }
        if (!owlOntologyRefsetFiles.isEmpty()) {
            owlExpressionRefsetFiles.addAll(owlOntologyRefsetFiles);
            owlOntologyRefsetFiles.clear();
        }
        if (!owlAxiomRefsetFiles.isEmpty()) {
            owlExpressionRefsetFiles.addAll(owlAxiomRefsetFiles);
            owlAxiomRefsetFiles.clear();
        }
        if (statedRelationshipsFiles.isEmpty() && owlExpressionRefsetFiles.isEmpty() && owlAxiomRefsetFiles.isEmpty() && owlOntologyRefsetFiles.isEmpty()) {
            throw new RuntimeException("No stated form of any kind declared.");
        }

        if (null == releaseType) {
            throw new RuntimeException("No releaseType.");
        }
        if (modules.size() < 1) {
            throw new RuntimeException("No modules.");
        }
    }

    /**
     * @return the conceptsFiles
     */
    public Set<String> getConceptsFiles() {
        return conceptsFiles;
    }

    /**
     * @param conceptsFiles the conceptsFiles to set
     */
    public void setConceptsFiles(Set<String> conceptsFiles) {
        this.conceptsFiles = conceptsFiles;
    }

    /**
     * @return the descriptionsFiles
     */
    public Set<String> getDescriptionsFiles() {
        return descriptionsFiles;
    }

    /**
     * @param descriptionsFiles the descriptionsFiles to set
     */
    public void setDescriptionsFiles(Set<String> descriptionsFiles) {
        this.descriptionsFiles = descriptionsFiles;
    }

    /**
     * @return the identifiersFiles
     */
    public Set<String> getIdentifiersFiles() {
        return identifiersFiles;
    }

    /**
     * @param identifiersFiles the identifiersFiles to set
     */
    public void setIdentifiersFiles(Set<String> identifiersFiles) {
        this.identifiersFiles = identifiersFiles;
    }

    /**
     * @return the relationshipsFiles
     */
    public Set<String> getNnfRelationshipsFiles() {
        return nnfRelationshipsFiles;
    }

    /**
     * @param relationshipsFiles the relationshipsFiles to set
     */
    public void setNnfRelationshipsFiles(Set<String> relationshipsFiles) {
        this.nnfRelationshipsFiles = relationshipsFiles;
    }

    /**
     * @return the concreteDomainsFiles
     */
    public Set<String> getNnfConcreteDomainsFiles() {
        return nnfConcreteDomainsFiles;
    }

    /**
     * @param relationshipsFiles the relationshipsFiles to set
     */
    public void setNnfConcreteDomainsFiles(Set<String> concreteDomainsFiles) {
        this.nnfConcreteDomainsFiles = concreteDomainsFiles;
    }

    /**
     * @return the statedRelationshipsFiles
     */
    public Set<String> getStatedRelationshipsFiles() {
        return statedRelationshipsFiles;
    }

    /**
     * @param statedRelationshipsFiles the statedRelationshipsFiles to set
     */
    public void setStatedRelationshipsFiles(
            Set<String> statedRelationshipsFiles) {
        this.statedRelationshipsFiles = statedRelationshipsFiles;
    }

    /**
     * @return the textDefinitionsFiles
     */
    public Set<String> getTextDefinitionsFiles() {
        return textDefinitionsFiles;
    }

    /**
     * @param textDefinitionsFiles the textDefinitionsFiles to set
     */
    public void setTextDefinitionsFile(Set<String> textDefinitionsFiles) {
        this.textDefinitionsFiles = textDefinitionsFiles;
    }

    public Set<String> getOwlExpressionRefsetFiles() {
        return owlExpressionRefsetFiles;
    }

    public void setOwlExpressionRefsetFiles(Set<String> owlExpressionRefsetFiles) {
        this.owlExpressionRefsetFiles = owlExpressionRefsetFiles;
    }

    /**
     * @return the refsetDescriptorRefsetFiles
     */
    public Set<String> getRefsetDescriptorRefsetFiles() {
        return refsetDescriptorRefsetFiles;
    }

    /**
     * @param refsetDescriptorRefsetFiles the refsetDescriptorRefsetFiles to set
     */
    public void setRefsetDescriptorRefsetFiles(
            Set<String> refsetDescriptorRefsetFiles) {
        this.refsetDescriptorRefsetFiles = refsetDescriptorRefsetFiles;
    }

    /**
     * @return the descriptionFormatRefsetFiles
     */
    public Set<String> getDescriptionFormatRefsetFiles() {
        return descriptionFormatRefsetFiles;
    }

    /**
     * @param descriptionFormatRefsetFiles the descriptionFormatRefsetFiles to set
     */
    public void setDescriptionFormatRefsetFiles(
            Set<String> descriptionFormatRefsetFiles) {
        this.descriptionFormatRefsetFiles = descriptionFormatRefsetFiles;
    }

    /**
     * @return the moduleDependenciesRefsetFiles
     */
    public Set<String> getModuleDependenciesRefsetFiles() {
        return moduleDependenciesRefsetFiles;
    }

    /**
     * @param moduleDependenciesRefsetFiles the moduleDependenciesRefsetFiles to set
     */
    public void setModuleDependenciesRefsetFiles(
            Set<String> moduleDependenciesRefsetFiles) {
        this.moduleDependenciesRefsetFiles = moduleDependenciesRefsetFiles;
    }

    /**
     * @return the languageRefsetFiles
     */
    public Set<String> getLanguageRefsetFiles() {
        return languageRefsetFiles;
    }

    /**
     * @param languageRefsetFiles the languageRefsetFiles to set
     */
    public void setLanguageRefsetFiles(Set<String> languageRefsetFiles) {
        this.languageRefsetFiles = languageRefsetFiles;
    }

    /**
     * @return the simpleRefsetFiles
     */
    public Set<String> getSimpleRefsetFiles() {
        return simpleRefsetFiles;
    }

    /**
     * @param simpleRefsetFiles the simpleRefsetFiles to set
     */
    public void setSimpleRefsetFiles(Set<String> simpleRefsetFiles) {
        this.simpleRefsetFiles = simpleRefsetFiles;
    }

    /**
     * @return the orderedRefsetFiles
     */
    public Set<String> getOrderedRefsetFiles() {
        return orderedRefsetFiles;
    }

    /**
     * @param orderedRefsetFiles the orderedRefsetFiles to set
     */
    public void setOrderedRefsetFiles(Set<String> orderedRefsetFiles) {
        this.orderedRefsetFiles = orderedRefsetFiles;
    }

    /**
     * @return the attributeValueRefsetFiles
     */
    public Set<String> getAttributeValueRefsetFiles() {
        return attributeValueRefsetFiles;
    }

    /**
     * @param attributeValueRefsetFiles the attributeValueRefsetFiles to set
     */
    public void setAttributeValueRefsetFiles(Set<String> attributeValueRefsetFiles) {
        this.attributeValueRefsetFiles = attributeValueRefsetFiles;
    }

    /**
     * @return the simpleMapRefsetFiles
     */
    public Set<String> getSimpleMapRefsetFiles() {
        return simpleMapRefsetFiles;
    }

    /**
     * @param simpleMapRefsetFiles the simpleMapRefsetFiles to set
     */
    public void setSimpleMapRefsetFiles(Set<String> simpleMapRefsetFiles) {
        this.simpleMapRefsetFiles = simpleMapRefsetFiles;
    }

    /**
     * @return the complexMapRefsetFiles
     */
    public Set<String> getComplexMapRefsetFiles() {
        return complexMapRefsetFiles;
    }

    /**
     * @param complexMapRefsetFiles the complexMapRefsetFiles to set
     */
    public void setComplexMapRefsetFiles(Set<String> complexMapRefsetFiles) {
        this.complexMapRefsetFiles = complexMapRefsetFiles;
    }

    /**
     * @return the querySpecificationRefsetFiles
     */
    public Set<String> getQuerySpecificationRefsetFiles() {
        return querySpecificationRefsetFiles;
    }

    /**
     * @param querySpecificationRefsetFiles the querySpecificationRefsetFiles to set
     */
    public void setQuerySpecificationRefsetFiles(
            Set<String> querySpecificationRefsetFiles) {
        this.querySpecificationRefsetFiles = querySpecificationRefsetFiles;
    }

    /**
     * @return the annotationRefsetFiles
     */
    public Set<String> getAnnotationRefsetFiles() {
        return annotationRefsetFiles;
    }

    /**
     * @param annotationRefsetFiles the annotationRefsetFiles to set
     */
    public void setAnnotationRefsetFiles(Set<String> annotationRefsetFiles) {
        this.annotationRefsetFiles = annotationRefsetFiles;
    }

    /**
     * @return the associationRefsetFiles
     */
    public Set<String> getAssociationRefsetFiles() {
        return associationRefsetFiles;
    }

    /**
     * @param associationRefsetFiles the associationRefsetFiles to set
     */
    public void setAssociationRefsetFiles(Set<String> associationRefsetFiles) {
        this.associationRefsetFiles = associationRefsetFiles;
    }

    /**
     * @return the concreteDomainRefsetFiles
     */
    public Set<String> getConcreteDomainRefsetFiles() {
        return concreteDomainRefsetFiles;
    }

    /**
     * @param concreteDomainRefsetFiles the concreteDomainRefsetFiles to set
     */
    public void setConcreteDomainRefsetFiles(Set<String> concreteDomainRefsetFiles) {
        this.concreteDomainRefsetFiles = concreteDomainRefsetFiles;
    }

    /**
     * @return the attributeDomainRefsetFiles
     */
    public Set<String> getAttributeDomainRefsetFiles() {
        return attributeDomainRefsetFiles;
    }

    /**
     * @param attributeDomainRefsetFiles the attributeDomainRefsetFiles to set
     */
    public void setAttributeDomainRefsetFiles(Set<String> attributeDomainRefsetFiles) {
        this.attributeDomainRefsetFiles = attributeDomainRefsetFiles;
    }

    /**
     * @return the releaseType
     */
    public ReleaseType getReleaseType() {
        return releaseType;
    }

    /**
     * @param releaseType the releaseType to set
     */
    public void setReleaseType(ReleaseType releaseType) {
        this.releaseType = releaseType;
    }

    /**
     * @return the modules
     */
    public List<ModuleInfo> getModules() {
        return modules;
    }

    /**
     * @param modules the modules to set
     */
    public void setModules(List<ModuleInfo> modules) {
        this.modules = modules;
    }

    public Set<String> getRelationshipsFiles() {
        return relationshipsFiles;
    }

    public void setRelationshipsFiles(Set<String> relationshipsFiles) {
        this.relationshipsFiles = relationshipsFiles;
    }

    public Set<String> getOwlOntologyRefsetFiles() {
        return owlOntologyRefsetFiles;
    }

    public void setOwlOntologyRefsetFiles(Set<String> owlOntologyRefsetFiles) {
        this.owlOntologyRefsetFiles = owlOntologyRefsetFiles;
    }

    public Set<String> getOwlAxiomRefsetFiles() {
        return owlAxiomRefsetFiles;
    }

    public void setOwlAxiomRefsetFiles(Set<String> owlAxiomRefsetFiles) {
        this.owlAxiomRefsetFiles = owlAxiomRefsetFiles;
    }

    public void setTextDefinitionsFiles(Set<String> textDefinitionsFiles) {
        this.textDefinitionsFiles = textDefinitionsFiles;
    }

} public enum StructuredLog {
    GenericException(null, "message", "exception"),

    OntologyGeneration("Building ontology", "id", "version"),
    UsingInferredRelationships("No stated relationships or OWL Axioms provided; using inferred relationships instead."),
    MissingMetadata("Metadata value for %s was not found. Import process might produce unexpected results.",
            "element"),

    ModuleLoadFailure("Unable to load module dependencies. Possibly broken input configuration file.",
            "inputType", "moduleId", "exception"),
    FileLoadFailure("Unable to load %s file. Possibly broken input configuration file.",
            "fileType", "inputType", "file", "exception"),
    IgnoredModules("Refset: Ignored data from module '%s' found in %s", "moduleId", "refsetFile"),

    RefsetLoadFailure("Error loading %s reference set file. Possibly has wrong number of columns.",
            "fileType", "file", "exception"),

    MissingRefsetId("Could not find refset id %s in meta-data hierarchy. There might be a problem with the concrete domains definitions.",
            "refsetId"),
    UntypedConcreteDomainRefsetId("Could not determine the type (int/float) of %s from position in hierarchy.",
            "refsetId"),

    DefinedWithoutParents("Concept %s has defining (non-ISA) relationships but no parents (ISA relationships).", "concept"),

    UndeclaredFeature("Ignoring feature %s (it has no type). There might be a problem with the concrete domains definitions.",
            "concept"),
    UnknownConcreteDomainType("Unknown concerete domain type: %s", "typeName"),
    UnknownConcreteDomainOperator("Unknown concerete domain operator: %s", "operatorId"),

    OWLUnknownReferencedComponent("Unexpected referencedComponentId %s (should be 734146004 or 734147008)", "refsetId"),
    OWLUnknownRefset("Unexpected refsetId %s (should be 733073007 or 762103008)", "refsetId"),

    InactiveDependency("Inactive dependency: %s to\t%s", "version", "requiredModule"),
    MalformedMDRSEntry("Ignoring malformed MDRS entry: %s to\t%s", "version", "requiredModule"),
    RefsetIdMismatch("refsetId does not match that for the MDRS (900000000000534007)"),
    TimesMismatch("effectiveTime and sourceEffectiveTime must be equal"),
    EffectgiveTimeOrderMismatch("effectiveTime cannot be earlier than sourceEffectiveTime"),
    SourceTimeMismatch("sourceEffectiveTime cannot be earlier than targetEffectiveTime"),

    InvalidEffectiveTime("Could not parse effectiveTime", "effectiveTime", "reasonTimestamp", "reasonDatestamp"),

    ImpliedTransitiveDependency("Added implied transitive dependency from %s to %s via %s",
            "srcModule", "targetModule", "transitiveModule"),
    CyclicDependency("Cyclic dependency"),

    GroupingError("Found grouped relationship for attribute type marked as never-grouped: %s", "attribute"),
    UngroupedConcreteDomains("Concrete domain data supplied (%s) for never-grouped attribute: %s", "relationshipId", "attribute"),

    None(null); // sentinal; do not use

    final private static String PREFIX = "json: ";

    final private ThreadLocal<UUID> uuid = ThreadLocal.withInitial(() -> UUID.randomUUID());

    final private String format;
    final private String[] keys;

    private StructuredLog(String format, String... keys) {
        this.format = format;
        this.keys = keys;
    }

    public String info(Logger log, Object... args) {
        return info((Map<String, Object>) null, log, args);
    }

    public String warn(Logger log, Object... args) {
        return warn((Map<String, Object>) null, log, args);
    }

    public String error(Logger log, Object... args) {
        return error((Map<String, Object>) null, log, args);
    }

    public String info(MapView obj, Logger log, Object... args) {
        return info(obj.toMap(), log, args);
    }

    public String warn(MapView obj, Logger log, Object... args) {
        return warn(obj.toMap(), log, args);
    }

    public String error(MapView obj, Logger log, Object... args) {
        return error(obj.toMap(), log, args);
    }

    public String info(Map<String, Object> map, Logger log, Object... args) {
        map = buildMap(log, map, args);
        log.info(PREFIX + renderMap(map));
        return (String) map.get("message");
    }

    public String warn(Map<String, Object> map, Logger log, Object... args) {
        map = buildMap(log, map, args);
        log.warn(PREFIX + renderMap(map));
        return (String) map.get("message");
    }

    public String error(Map<String, Object> map, Logger log, Object... args) {
        map = buildMap(log, map, args);
        log.error(PREFIX + renderMap(map));
        return (String) map.get("message");
    }

    public Map<String, Object> buildMap(Logger log, MapView obj, Object... args) {
        return buildMap(log, obj.toMap(), args);
    }

    public void reset() {
        uuid.set(UUID.randomUUID());
    }

    private Map<String, Object> buildMap(Logger log, Map<String, Object> map, Object... args) {
        final String message = format == null ? null : String.format(format, args);
        if (null == map) {
            map = new HashMap<>();
        }
        map.put("uuid", uuid.get());
        if (null != keys) {
            for (int i = 0; i < keys.length; i++) {
                map.put(keys[i], args[i]);
            }
        }
        if (null != message) {
            map.put("message", message);
        }
        map.put("logContext", log.getName());
        return map;
    }

    public static String renderMap(Map<String, Object> args) {
        final StringBuilder sb = new StringBuilder("{");
        boolean first = true;

        for (Map.Entry<String,Object> entry : args.entrySet()) {
            Object value = entry.getValue();
            value = renderValue(value);
            if (first) {
                first = false;
            } else {
                sb.append(", ");
            }
            sb.append("\"").append(entry.getKey()).append("\": ").append(value);
        }
        sb.append("}");
        return sb.toString();
    }

    private static String renderValue(Object value) {
        if (null == value) {
            value = "";
        } else if (value instanceof Collection<?>) {
            StringBuffer newValue = new StringBuffer("[");
            boolean first = true;
            for (Object contained : ((Collection<?>) value)) {
                if (!first) {
                    newValue.append(',');
                } else {
                    first = false;
                }
                newValue.append(renderValue(contained));
            }
            newValue.append("]");
            return newValue.toString();
        } else if (value instanceof Map<?,?>) {
            return renderMap((Map<String, Object>) value);
        } else if (value instanceof MapView) {
            return renderMap(((MapView) value).toMap());
        }

        if (value == null || value.toString() == null) {
            return "\"\"";
        }

        if (!(value instanceof Integer || value instanceof Long || value instanceof Boolean)) {
            value = value.toString().replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\t", "\\t");
            value = "\"" + value + "\"";
        }

        return value.toString();
    }

} public class Version {
    protected String id;
    protected Map<String, String> metadata = new HashMap<String, String>();
    
    public Version() {
        
    }
    
    public Version(String id) {
        super();
        this.id = id;
    }

    /**
     * @return the name
     */
    public String getId() {
        return id;
    }

    /**
     * @return the values
     */
    public Map<String, String> getMetadata() {
        return metadata;
    }

    /**
     * @param id the id to set
     */
    public void setId(String id) {
        this.id = id;
    }

    /**
     * @param metadata the metadata to set
     */
    public void setMetadata(Map<String, String> metadata) {
        this.metadata = metadata;
    }
    
} public abstract class Axiom implements Comparable<Axiom> {

}
 public abstract class Concept implements Comparable<Concept>, Serializable {
    
    private static final long serialVersionUID = 1L;

    public abstract String toString();

    public abstract int hashCode();

    public abstract boolean equals(Object o);
    
} @XmlRootElement
public class ConceptInclusion extends Axiom {
    
    /**
     * The left hand side of the expression.
     */
    private Concept lhs;
    
    /**
     * The right hand side of the expression.
     */
    private Concept rhs;
    
    /**
     * 
     */
    public ConceptInclusion() {
        
    }
    
    /**
     * Creates a new ConceptInclusion.
     * 
     * @param lhs
     * @param rhs
     */
    public ConceptInclusion(final Concept lhs, final Concept rhs) {
        if (null == lhs) {
            throw new IllegalArgumentException("LHS cannot be null (RHS = " + rhs + ")");
        }
        this.lhs = lhs;
        if (null == rhs) {
            throw new IllegalArgumentException("RHS cannot be null (LHS = " + lhs + ")");
        }
        this.rhs = rhs;
    }
    
    

    @Override
    public String toString() {
        return lhs + " \u2291 " + rhs;
    }

    /**
     * @return the lhs
     */
    public Concept getLhs() {
        return lhs;
    }

    /**
     * @param lhs the lhs to set
     */
    public void setLhs(Concept lhs) {
        this.lhs = lhs;
    }

    /**
     * @return the rhs
     */
    public Concept getRhs() {
        return rhs;
    }

    /**
     * @param rhs the rhs to set
     */
    public void setRhs(Concept rhs) {
        this.rhs = rhs;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((lhs == null) ? 0 : lhs.hashCode());
        result = prime * result + ((rhs == null) ? 0 : rhs.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        ConceptInclusion other = (ConceptInclusion) obj;
        if (lhs == null) {
            if (other.lhs != null)
                return false;
        } else if (!lhs.equals(other.lhs))
            return false;
        if (rhs == null) {
            if (other.rhs != null)
                return false;
        } else if (!rhs.equals(other.rhs))
            return false;
        return true;
    }

    public int compareTo(Axiom o) {
        if(!(o instanceof ConceptInclusion)) {
            return -1;
        } else {
            ConceptInclusion otherCi = (ConceptInclusion) o;
            int lhsRes = lhs.compareTo(otherCi.getLhs());
            int rhsRes = rhs.compareTo(otherCi.getRhs());
            if(lhsRes == 0 && rhsRes == 0)
                return 0;
            else if(lhsRes != 0)
                return lhsRes;
            else
                return rhsRes;
        }
    }
    
} @XmlRootElement
public class Conjunction extends Concept {

    private static final long serialVersionUID = 1L;

    private Concept[] concepts;

    private int hashCode;

    /**
     *
     */
    @SuppressWarnings("unchecked")
    public Conjunction() {
        this(Collections.EMPTY_SET);
    }

    /**
     *
     * @param concepts
     */
    public Conjunction(final Concept[] concepts) {
        setConcepts(concepts);
    }

    public Conjunction(final Collection<? extends Concept> concepts) {
        // Store the concepts in hashCode order so that equals() is order
        // independent, i.e. conjunctions are reflexive (should also be
        // transitive, but Agile says STTCPW)

        final SortedSet<Concept> sorted = new TreeSet<>(concepts);
        this.concepts = sorted.toArray(new Concept[sorted.size()]);
        hashCode = sorted.hashCode();
    }

    public Concept[] getConcepts() {
        return concepts;
    }

    /**
     * @param concepts the concepts to set
     */
    public void setConcepts(Concept[] concepts) {
        final SortedSet<Concept> sorted = new TreeSet<>();
        for (Concept concept : concepts) {
            sorted.add(concept);
        }
        this.concepts = sorted.toArray(new Concept[sorted.size()]);
        hashCode = sorted.hashCode();
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("(");
        if (concepts.length > 0) {
            sb.append(concepts[0]);
            for (int i = 1; i < concepts.length; i++) {
                sb.append(" + ");
                sb.append(concepts[i]);
            }
        }
        sb.append(")");
        return sb.toString();
    }

    @Override
    public int hashCode() {
        return hashCode;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        final Conjunction other = (Conjunction) obj;
        return hashCode == other.hashCode
                && Arrays.equals(concepts, other.concepts);
    }

    @Override
    public int compareTo(Concept o) {
        Class<? extends Conjunction> thisClass = this.getClass();
        Class<? extends Concept> otherClass = o.getClass();
        if(thisClass.equals(otherClass)) {
            Conjunction other = (Conjunction)o;
            // Equal if all concepts equal
            // Otherwise order depends on the length and then on the order of
            // first different concept

            int res = 0;
            res = concepts.length - other.concepts.length;
            if(res != 0) return res;

            for(int i = 0; i < concepts.length; i++) {
                try {
                    res = concepts[i].compareTo(other.concepts[i]);
                } catch(ClassCastException e) {
                    // Need to catch this because elements in the conjunction
                    // might be of different types
                    res = concepts[i].getClass().toString().compareTo(
                            other.concepts[i].getClass().toString());
                }

                if(res != 0) return res;
            }

            return 0;
        } else {
            return thisClass.toString().compareTo(otherClass.toString());
        }
    }

} @XmlRootElement
public class Datatype extends Concept {

    private static final long serialVersionUID = 1L;
    
    private Feature feature;
    
    private Operator operator;
    
    private Literal literal;
    
    /**
     * 
     */
    public Datatype() {
        
    }
    
    /**
     * 
     * @param feature
     * @param operator
     * @param literal
     */
    public Datatype(Feature feature, Operator operator, Literal literal) {
        this.feature = feature;
        this.operator = operator;
        this.literal = literal;
    }

    public Feature getFeature() {
        return feature;
    }

    public Operator getOperator() {
        return operator;
    }

    public Literal getLiteral() {
        return literal;
    }

    /**
     * @param feature the feature to set
     */
    public void setFeature(Feature feature) {
        this.feature = feature;
    }

    /**
     * @param operator the operator to set
     */
    public void setOperator(Operator operator) {
        this.operator = operator;
    }

    /**
     * @param literal the literal to set
     */
    public void setLiteral(Literal literal) {
        this.literal = literal;
    }

    @Override
    public String toString() {
        return feature + ".(" + operator + "," + literal + ")";
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((feature == null) ? 0 : feature.hashCode());
        result = prime * result + ((literal == null) ? 0 : literal.hashCode());
        result = prime * result + ((operator == null) ? 0 : operator.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Datatype other = (Datatype) obj;
        if (feature == null) {
            if (other.feature != null)
                return false;
        } else if (!feature.equals(other.feature))
            return false;
        if (literal == null) {
            if (other.literal != null)
                return false;
        } else if (!literal.equals(other.literal))
            return false;
        if (operator != other.operator)
            return false;
        return true;
    }

    @SuppressWarnings({ "rawtypes" })
    public int compareTo(Concept o) {
        Class thisClass = this.getClass();
        Class otherClass = o.getClass();
        if(thisClass.equals(otherClass)) {
            Datatype other = (Datatype)o;
            int res = 0;
            res = feature.compareTo(other.feature);
            if(res != 0) return res;
            res = operator.compareTo(other.operator);
            if(res != 0) return res;
            res = literal.compareTo(other.literal);
            return res;
        } else {
            return thisClass.toString().compareTo(otherClass.toString());
        }
    }

} @XmlRootElement
public class DecimalLiteral extends Literal {

    private static final long serialVersionUID = 1L;
    
    private BigDecimal value;
    
    /**
     * 
     */
    public DecimalLiteral() {
        
    }
    
    /**
     * 
     * @param type
     * @param value
     */
    public DecimalLiteral(BigDecimal value) {
        this.value = value;
    }
    
    /**
     * 
     * @param value
     */
    public DecimalLiteral(double value) {
        this.value = BigDecimal.valueOf(value);
    }

    /**
     * @return the value
     */
    public BigDecimal getValue() {
        return value;
    }

    /**
     * @param value the value to set
     */
    public void setValue(BigDecimal value) {
        this.value = value;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((value == null) ? 0 : value.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        DecimalLiteral other = (DecimalLiteral) obj;
        if (value == null) {
            if (other.value != null)
                return false;
        } else if (!value.equals(other.value))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return String.valueOf(value);
    }

    public int compareTo(Literal o) {
        return value.compareTo(((DecimalLiteral) o).value);
    }

}
 @XmlRootElement
public class Existential extends Concept {

    private static final long serialVersionUID = 1L;
    
    private Role role;
    
    private Concept concept;
    
    /**
     * 
     */
    public Existential() {
        
    }
    
    /**
     * 
     * @param role
     * @param concept
     */
    public Existential(Role role, Concept concept) {
        this.role = role;
        this.concept = concept;
    }

    @Override
    public String toString() {
        return role + "." + concept;
    }
    
    public Role getRole() {
        return role;
    }
    
    public Concept getConcept() {
        return concept;
    }

    /**
     * @param role the role to set
     */
    public void setRole(Role role) {
        this.role = role;
    }

    /**
     * @param concept the concept to set
     */
    public void setConcept(Concept concept) {
        this.concept = concept;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((concept == null) ? 0 : concept.hashCode());
        result = prime * result + ((role == null) ? 0 : role.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Existential other = (Existential) obj;
        if (concept == null) {
            if (other.concept != null)
                return false;
        } else if (!concept.equals(other.concept))
            return false;
        if (role == null) {
            if (other.role != null)
                return false;
        } else if (!role.equals(other.role))
            return false;
        return true;
    }

    @SuppressWarnings({ "rawtypes" })
    public int compareTo(Concept o) {
        Class thisClass = this.getClass();
        Class otherClass = o.getClass();
        if(thisClass.equals(otherClass)) {
            Existential other = (Existential)o;
            int res = 0;
            res = role.compareTo(other.role);
            if(res != 0) return res;
            try {
                res = concept.compareTo(other.concept);
            } catch(ClassCastException e) {
                // Need to catch this because elements in the conjunction might
                // be of different types
                res = concept.getClass().toString().compareTo(
                        other.concept.getClass().toString());
            }
            if(res != 0) return res;
            return 0;
        } else {
            return thisClass.toString().compareTo(otherClass.toString());
        }
    }

} public class FunctionalFeature extends Axiom {

    private Feature feature;

    public FunctionalFeature() {
    }

    public FunctionalFeature(Feature feature) {
        this.feature = feature;
    }

    public void setFeature(Feature feature) {
        this.feature = feature;
    }

    public Feature getFeature() {
        return feature;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((feature == null) ? 0 : feature.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        FunctionalFeature other = (FunctionalFeature) obj;
        if (feature == null) {
            if (other.feature != null)
                return false;
        } else if (!feature.equals(other.feature))
            return false;
        return true;
    }

    @Override
    public int compareTo(Axiom o) {
        if (!(o instanceof FunctionalFeature)) {
            return 1;
        } else {
            return 0;
        }
    }

} @XmlRootElement
public class IntegerLiteral extends Literal {

    private static final long serialVersionUID = 1L;
    
    private int value;
    
    /**
     * 
     */
    public IntegerLiteral() {
        
    }
    
    /**
     * Constructor.
     * 
     * @param type
     */
    public IntegerLiteral(int value) {
        this.value = value;
    }

    /**
     * @return the value
     */
    public int getValue() {
        return value;
    }

    /**
     * @param value the value to set
     */
    public void setValue(int value) {
        this.value = value;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + value;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        IntegerLiteral other = (IntegerLiteral) obj;
        if (value != other.value)
            return false;
        return true;
    }

    @Override
    public String toString() {
        return String.valueOf(value);
    }

    public int compareTo(Literal o) {
        return ((Integer) value).compareTo(((IntegerLiteral) o).value);
    }

} public abstract class Literal implements Comparable<Literal>, Serializable {
    
    private static final long serialVersionUID = 1L;
    
    public abstract String toString();

    public abstract int hashCode();

    public abstract boolean equals(Object o);

} @XmlRootElement
public class NamedConcept extends Concept {

    private static final long serialVersionUID = 1L;

    /**
     * Represents the top concept.
     */
    public static String TOP = "_TOP_";
    
    /**
     * Represents the bottom concept.
     */
    public static String BOTTOM = "_BOTTOM_";
    
    /**
     * The top concept.
     */
    public static final Concept TOP_CONCEPT = new NamedConcept(TOP);
    
    /**
     * The bottom concept.
     */
    public static final Concept BOTTOM_CONCEPT = new NamedConcept(BOTTOM);
    
    /**
     * String identifier of this concept.
     */
    protected String id;
    
    /**
     * No args constructor.
     */
    public NamedConcept() {
        
    }
    
    /**
     * Creates a new Concept.
     * 
     * @param id
     *            The concept's identifier.
     */
    public NamedConcept(String id) {
        assert(id != null);
        this.id = id;
    }
    
    /**
     * Returns this concept's identifier.
     * 
     * @return The identifier.
     */
    public String getId() {
        return id;
    }

    /**
     * @param id the id to set
     */
    public void setId(String id) {
        this.id = id;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("Concept [id=");
        builder.append(id);
        builder.append("]");
        return builder.toString();
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        return result;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        NamedConcept other = (NamedConcept) obj;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        return true;
    }

    @Override
    public int compareTo(Concept other) {
        if(!(other instanceof NamedConcept)) {
            return -1;
        }
        return id.compareTo(((NamedConcept) other).id);
    }
    
} @XmlRootElement
public class NamedFeature extends Feature {

    private static final long serialVersionUID = 1L;
    
    /**
     * String identifier of this concept.
     */
    private String id;
    
    /**
     * 
     */
    public NamedFeature() {
        
    }
    
    /**
     * Creates a new Concept.
     * 
     * @param id
     *            The concept's identifier.
     */
    public NamedFeature(String id) {
        assert(id != null);
        this.id = id;
    }

    /**
     * Returns this concept's identifier.
     * 
     * @return The identifier.
     */
    public String getId() {
        return id;
    }

    /**
     * @param id the id to set
     */
    public void setId(String id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return id.toString();
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        NamedFeature other = (NamedFeature) obj;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        return true;
    }

    @Override
    public int compareTo(Feature o) {
        if(!(o instanceof NamedFeature)) {
            return -1;
        } else {
            return id.compareTo(((NamedFeature) o).getId());
        }
    }

} @XmlRootElement
public class NamedRole extends Role {
    
    private static final long serialVersionUID = 1L;
    
    /**
     * String identifier of this concept.
     */
    protected String id;
    
    /**
     * 
     */
    public NamedRole() {
        
    }
    
    /**
     * Creates a new Role.
     * 
     * @param id
     *            The role's identifier.
     */
    public NamedRole(String id) {
        assert(id != null);
        this.id = id;
    }

    /**
     * Returns this role's identifier.
     * 
     * @return The identifier.
     */
    public String getId() {
        return id;
    }

    /**
     * @param id the id to set
     */
    public void setId(String id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return id.toString();
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        NamedRole other = (NamedRole) obj;
        if (id == null) {
            if (other.id != null)
                return false;
            else
                assert(false);
        } else if (!id.equals(other.id))
            return false;
        return true;
    }

    public int compareTo(Role o) {
        if(!(o instanceof NamedRole)) {
            return -1;
        } else {
            return id.compareTo(((NamedRole) o).getId());
        }
    }

} public enum Operator {
    EQUALS,
    LESS_THAN,
    LESS_THAN_EQUALS,
    GREATER_THAN,
    GREATER_THAN_EQUALS
} public abstract class Role implements Comparable<Role>, Serializable {
    
    private static final long serialVersionUID = 1L;

    abstract public String toString();

    abstract public int hashCode();

    abstract public boolean equals(Object o);
    
} @XmlRootElement
public class RoleInclusion extends Axiom {

    /**
     * The left hand side of the expression.
     */
    protected Role[] lhs;

    /**
     * The right hand side of the expression.
     */
    protected Role rhs;

    /**
     *
     */
    public RoleInclusion() {

    }

    /**
     * Creates a new {@link RoleInclusion}.
     *
     * @param lhs
     * @param rhs
     */
    public RoleInclusion(final Role[] lhs, final Role rhs) {
        this.lhs = lhs;
        this.rhs = rhs;
    }

    /**
     * Creates a new {@link RoleInclusion}.
     *
     * @param lhs
     * @param rhs
     */
    public RoleInclusion(final Role lhs, final Role rhs) {
        this.lhs = new Role[]{ lhs };
        this.rhs = rhs;
    }

    /**
     * @return the lhs
     */
    public Role[] getLhs() {
        return lhs;
    }

    /**
     * @param lhs the lhs to set
     */
    public void setLhs(Role[] lhs) {
        this.lhs = lhs;
    }

    /**
     * @return the rhs
     */
    public Role getRhs() {
        return rhs;
    }

    /**
     * @param rhs the rhs to set
     */
    public void setRhs(Role rhs) {
        this.rhs = rhs;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        if (lhs.length > 0) {
            sb.append(lhs[0]);
            for (int i = 1; i < lhs.length; i++) {
                sb.append(" o ");
                sb.append(lhs[i]);
            }
        } else {
            sb.append("\u03B5");
        }
        sb.append(" \u2291 ");
        sb.append(rhs);
        return sb.toString();
    }

    @Override
    public int compareTo(Axiom o) {
        if (o instanceof ConceptInclusion) {
            return 1;
        } else if (o instanceof RoleInclusion) {
            RoleInclusion otherRi = (RoleInclusion) o;
            int lhsRes = 0;
            Role[] oLhs = otherRi.getLhs();
            if(!Arrays.equals(lhs, oLhs)) {
                // If length is different put the shortest one first
                if(lhs.length < oLhs.length) {
                    lhsRes = -1;
                } else if(lhs.length > oLhs.length) {
                    lhsRes = 1;
                } else {
                    for(int i = 0; i < lhs.length; i++) {
                        int res = lhs[i].compareTo(oLhs[i]);
                        if(res < 0) {
                            lhsRes = -1;
                            break;
                        } else if(res > 0) {
                            lhsRes = 1;
                            break;
                        }
                    }
                }
            }
            int rhsRes = rhs.compareTo(otherRi.getRhs());
            if(lhsRes == 0 && rhsRes == 0)
                return 0;
            else if(lhsRes != 0)
                return lhsRes;
            else
                return rhsRes;
        } else {
            return -1;
        }
    }

} public interface IModuleDependencyRefset extends IRefset {
    
    /**
     * Returns a {@link Map} of root module ids and a {@link Map} of 
     * {@link IModule}s indexed by version. Each {@link ModuleDependency} 
     * represents a versioned module and its dependencies.
     * 
     * @return
     */
    public Map<String, Map<String, ModuleDependency>> getModuleDependencies();
    
} public class ModuleDependency implements MapView {

    final protected String id;
    final protected String version;
    final protected Set<ModuleDependency> dependencies = new HashSet<ModuleDependency>();

    /**
     * Creates a new module.
     *
     * @param id not null
     * @param version not null
     */
    public ModuleDependency(String id, String version) {
        if (id == null) {
            throw new IllegalArgumentException("Module id cannot be null");
        }
        if (version == null) {
            throw new IllegalArgumentException("Module version cannot be null");
        }

        this.id = id;
        this.version = version;
    }

    public String getId() {
        return id;
    }

    public String getVersion() {
        return version;
    }

    public Collection<ModuleDependency> getDependencies() {
        return dependencies;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + id.hashCode();
        result = prime * result + version.hashCode();
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        final ModuleDependency other = (ModuleDependency) obj;
        return id.equals(other.id) && version.equals(other.version);
    }

    @Override
    public String toString() {
        return StructuredLog.renderMap(toMap());
    }

    @Override
    public Map<String, Object> toMap() {
        final Map<String, Object> map = new HashMap<>();
        map.put("type", "ModuleDependency");
        map.put("id", id);
        map.put("version", version);
        map.put("dependencies", dependencies);
        return map;
    }

} public class RefsetRow implements MapView, Comparable<RefsetRow> {

    private static final String[] NONE = {};

    protected final String id;
    protected final String effectiveTime;
    protected final String active;
    protected final String moduleId;
    protected final String refsetId;
    protected final String referencedComponentId;
    protected final String[] extras;

    /**
     * Constructor.
     *
     * @param id
     * @param effectiveTime
     * @param active
     * @param moduleId
     * @param refsetId
     * @param referencedComponentId
     */
    public RefsetRow(String id, String effectiveTime, String active, String moduleId, String refsetId,
            String referencedComponentId, String... extras) {
        super();
        this.id = id;
        this.effectiveTime = effectiveTime;
        this.active = active;
        this.moduleId = moduleId;
        this.refsetId = refsetId;
        this.referencedComponentId = referencedComponentId;
        this.extras = extras == null ? NONE : extras;

        assert extras.length == getColumns().length;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((active == null) ? 0 : active.hashCode());
        result = prime * result
                + ((effectiveTime == null) ? 0 : effectiveTime.hashCode());
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        result = prime * result
                + ((moduleId == null) ? 0 : moduleId.hashCode());
        result = prime
                * result
                + ((referencedComponentId == null) ? 0 : referencedComponentId
                        .hashCode());
        result = prime * result
                + ((refsetId == null) ? 0 : refsetId.hashCode());
        result = prime * result
                + ((extras == null) ? 0 : extras.hashCode());
        return result;
    }

    /**
     * @return the names of the extra columns
     */
    public String[] getColumns() {
        return NONE;
    }

    /**
     * @return the id
     */
    public String getId() {
        return id;
    }

    /**
     * @return the effectiveTime
     */
    public String getEffectiveTime() {
        return effectiveTime;
    }

    /**
     * @return the active
     */
    public String getActive() {
        return active;
    }

    /**
     * @return the moduleId
     */
    public String getModuleId() {
        return moduleId;
    }

    /**
     * @return the refsetId
     */
    public String getRefsetId() {
        return refsetId;
    }

    /**
     * @return the referencedComponentId
     */
    public String getReferencedComponentId() {
        return referencedComponentId;
    }

    /**
     * @return the extra columns
     */
    public String[] getExtras() {
        return extras;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        RefsetRow other = (RefsetRow) obj;
        if (active == null) {
            if (other.active != null)
                return false;
        } else if (!active.equals(other.active))
            return false;
        if (effectiveTime == null) {
            if (other.effectiveTime != null)
                return false;
        } else if (!effectiveTime.equals(other.effectiveTime))
            return false;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        if (moduleId == null) {
            if (other.moduleId != null)
                return false;
        } else if (!moduleId.equals(other.moduleId))
            return false;
        if (referencedComponentId == null) {
            if (other.referencedComponentId != null)
                return false;
        } else if (!referencedComponentId.equals(other.referencedComponentId))
            return false;
        if (refsetId == null) {
            if (other.refsetId != null)
                return false;
        } else if (!refsetId.equals(other.refsetId))
            return false;
        if (extras == null) {
            if (other.extras != null)
                return false;
        } else if (!extras.equals(other.extras))
            return false;
        return true;
    }

    @Override
    public int compareTo(RefsetRow other) {
        if(this.equals(other))
            return 0;
        else {
            int res = effectiveTime.compareTo(other.effectiveTime);
            if(res != 0) return res;
            res = moduleId.compareTo(other.moduleId);
            if(res != 0) return res;
            res = id.compareTo(other.id);
            if(res != 0) return res;
            res = active.compareTo(other.active);
            if(res != 0) return res;
            res = refsetId.compareTo(other.refsetId);
            if(res != 0) return res;
            res = referencedComponentId.compareTo(other.referencedComponentId);
            if(res != 0) return res;
            res = extras.length - other.extras.length;
            if(res != 0) return res;
            for (int i = 0; i < extras.length; i++) {
                res = extras[i].compareTo(other.extras[i]);
                if(res != 0) return res;
            }
            return res;
        }
    }

    @Override
    public Map<String, Object> toMap() {
        Map<String, Object> map = new HashMap<>();
        map.put("id", id);
        map.put("effectiveTime", effectiveTime);
        map.put("active", active);
        map.put("moduleId", moduleId);
        map.put("refsetId", refsetId);
        map.put("referencedComponentId", referencedComponentId);
        map.put("extras", extras);
        return map ;
    }

}
 public interface IProgressMonitor {
    
    /**
     * Indicates that some task has started.
     */
    void taskStarted(String taskName);

    /**
     * Indicates that the previously started task has now ended.
     */
    void taskEnded();

    /**
     * Indicates that there has been progress in a task.
     *  
     * @param value
     * @param max
     */
    void step(int value, int max);

    /**
     * Indicates that there is a task in progress whose length cannot be
     * determined.
     */
    void taskBusy();
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import org.semanticweb.owlapi.apibinding.OWLManager;
import org.semanticweb.owlapi.formats.FunctionalSyntaxDocumentFormat;
import org.semanticweb.owlapi.io.OWLOntologyDocumentSource;
import org.semanticweb.owlapi.io.ReaderDocumentSource;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLDocumentFormat;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyCreationException;
import org.semanticweb.owlapi.model.OWLOntologyManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import au.csiro.ontology.Ontology;
import au.csiro.ontology.importer.BaseImporter;
import au.csiro.ontology.importer.ImportException;
import au.csiro.ontology.importer.owl.OWLImporter;
import au.csiro.ontology.input.Input;
import au.csiro.ontology.input.Input.InputType;
import au.csiro.ontology.input.Inputs;
import au.csiro.ontology.input.ModuleInfo;
import au.csiro.ontology.input.RF2Input;
import au.csiro.ontology.input.StructuredLog;
import au.csiro.ontology.input.Version;
import au.csiro.ontology.model.Axiom;
import au.csiro.ontology.model.Concept;
import au.csiro.ontology.model.ConceptInclusion;
import au.csiro.ontology.model.Conjunction;
import au.csiro.ontology.model.Datatype;
import au.csiro.ontology.model.DecimalLiteral;
import au.csiro.ontology.model.Existential;
import au.csiro.ontology.model.FunctionalFeature;
import au.csiro.ontology.model.IntegerLiteral;
import au.csiro.ontology.model.Literal;
import au.csiro.ontology.model.NamedConcept;
import au.csiro.ontology.model.NamedFeature;
import au.csiro.ontology.model.NamedRole;
import au.csiro.ontology.model.Operator;
import au.csiro.ontology.model.Role;
import au.csiro.ontology.model.RoleInclusion;
import au.csiro.ontology.snomed.refset.rf2.IModuleDependencyRefset;
import au.csiro.ontology.snomed.refset.rf2.ModuleDependency;
import au.csiro.ontology.snomed.refset.rf2.RefsetRow;
import au.csiro.ontology.util.IProgressMonitor;
/**
 * Copyright CSIRO Australian e-Health Research Centre (http://aehrc.com).
 * All rights reserved. Use is subject to license terms and conditions.
 */
package au.csiro.ontology.importer.rf2;




/**
 * Imports ontologies specified in RF2 format into the internal representation.
 *
 * @author Alejandro Metke
 *
 */
public class RF2Importer extends BaseImporter {

    protected static final OWLDocumentFormat FUNCTIONAL_SYNTAX_DOCUMENT_FORMAT = new FunctionalSyntaxDocumentFormat();

    /**
     * Logger.
     */
    private final static Logger log = LoggerFactory.getLogger(RF2Importer.class);

    /**
     * List of problems found while importing.
     */
    protected final List<String> problems = new ArrayList<>();

    /**
     * Queue with the inputs to process.
     */
    protected final Queue<RF2Input> inputs = new LinkedList<>();

    /**
     * Imports an ontology using the supplied configuration object.
     *
     * @param input
     */
    public RF2Importer(RF2Input input) {
        this.inputs.add(input);
    }

    /**
     * Imports a set of ontologies using the supplied configuration object.
     *
     * @param inputs
     */
    public RF2Importer(Collection<Input> inputs) {
        for(Input in : inputs) {
            if(in instanceof RF2Input) {
                this.inputs.add((RF2Input) in);
            }
        }
    }

    @Override
    public Iterator<Ontology> getOntologyVersions(IProgressMonitor monitor) throws ImportException {
        return new OntologyInterator(monitor);
    }

    @Override
    public List<String> getProblems() {
        return problems;
    }

    /**
     * Loads all the module dependency information from all RF2 inputs into a single {@link IModuleDependencyRefset}.
     *
     * @return
     * @throws ImportException
     */
    protected IModuleDependencyRefset loadModuleDependencies(RF2Input input) throws ImportException {
        Set<InputStream> iss = new HashSet<>();
        InputType inputType = input.getInputType();
        for(String md : input.getModuleDependenciesRefsetFiles()) {
            try {
                iss.add(input.getInputStream(md));
            } catch (NullPointerException | IOException e) {
                final String message = StructuredLog.ModuleLoadFailure.error(log, inputType, md, e);