001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.rdf.owl2java;
031
032import java.net.MalformedURLException;
033import java.net.URL;
034import java.util.ArrayList;
035import java.util.HashMap;
036import java.util.HashSet;
037import java.util.List;
038import java.util.Set;
039
040import javax.xml.bind.PropertyException;
041
042import org.apache.commons.lang.WordUtils;
043import org.openimaj.rdf.owl2java.Generator.GeneratorOptions;
044import org.openimaj.rdf.serialize.Predicate;
045import org.openrdf.model.URI;
046import org.openrdf.model.Value;
047import org.openrdf.model.impl.URIImpl;
048import org.openrdf.query.Binding;
049import org.openrdf.query.BindingSet;
050import org.openrdf.query.MalformedQueryException;
051import org.openrdf.query.QueryEvaluationException;
052import org.openrdf.query.QueryLanguage;
053import org.openrdf.query.TupleQuery;
054import org.openrdf.query.TupleQueryResult;
055import org.openrdf.repository.RepositoryConnection;
056import org.openrdf.repository.RepositoryException;
057import org.openrdf.sail.memory.model.MemBNode;
058import org.openrdf.sail.memory.model.MemStatement;
059import org.openrdf.sail.memory.model.MemStatementList;
060
061/**
062 * Represents the definition of a property of a class.
063 *
064 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
065 * @author David Dupplaw (dpd@ecs.soton.ac.uk)
066 * @created 29 Oct 2012
067 * @version $Author$, $Revision$, $Date$
068 */
069public class PropertyDef
070{
071
072        private GeneratorOptions generator;
073
074        /**
075         * @param go
076         */
077        public PropertyDef(final GeneratorOptions go) {
078                try {
079                        this.generator = (GeneratorOptions) go.clone();
080                        this.generator.skipPom = true;
081                } catch (final CloneNotSupportedException e) {
082                }
083        }
084
085        /** A map of XML Schema types to Java types */
086        protected static HashMap<URI, String> typeMap = new HashMap<URI, String>();
087
088        /** A map of XML Schema types to imports */
089        protected static HashMap<URI, String> importMap = new HashMap<URI, String>();
090
091        /**
092         * A map of URIs which must be resolved with the provided generator if it
093         * exists
094         */
095        protected static HashMap<URI, URL> uriResolveMap = new HashMap<URI, URL>();
096
097        /** We'll set up the importMap and typeMap here */
098        static
099        {
100                // --------- Import Maps -------- //
101                // Some of the XMLSchema types will need specific imports for them to
102                // work. For example, xsd:dateTime will become a DateTime. This requires
103                // an import (or a mapping). Other types may need to be mapped (below)
104                // and imported (using imports defined here). Note that the generator
105                // will add ".*" to the end of the import strings.
106                PropertyDef.importMap.put(
107                                new URIImpl("http://www.w3.org/2001/XMLSchema#date"),
108                                "org.joda.time.DateTime");
109
110                PropertyDef.importMap.put(
111                                new URIImpl("http://www.w3.org/2001/XMLSchema#dateTime"),
112                                "org.joda.time.DateTime");
113
114                // ----------- Type Maps ---------- //
115                // XMLSchema types will be converted into Java types by capitalising the
116                // first letter of the XMLSchema type (and removing the namespace).
117                // So simple types will just work -> string=String, float=Float.
118                // Some won't work int=Integer. It may be necessary to map some
119                // other URIs to specific types here.
120                PropertyDef.typeMap.put(
121                                new URIImpl("http://www.w3.org/2001/XMLSchema#int"),
122                                "Integer");
123                PropertyDef.typeMap.put(
124                                new URIImpl("http://www.w3.org/2001/XMLSchema#int"),
125                                "Integer");
126                PropertyDef.typeMap.put(
127                                new URIImpl("http://www.w3.org/2000/01/rdf-schema#Literal"),
128                                "String");
129                PropertyDef.typeMap.put(
130                                new URIImpl("http://www.w3.org/2001/XMLSchema#nonNegativeInteger"),
131                                "Integer");
132                PropertyDef.typeMap.put(
133                                new URIImpl("http://www.w3.org/2001/XMLSchema#date"),
134                                "DateTime");
135
136                // ----------- URL Maps ---------- //
137                // Some URIs need further semantics to make sense. These semantic can
138                // be resolved from this map
139                try {
140                        PropertyDef.uriResolveMap.put(new URIImpl("http://www.w3.org/2004/03/trix/rdfg-1/Graph"), new URL(
141                                        "http://www.w3.org/2004/03/trix/rdfg-1/Graph"));
142                } catch (final MalformedURLException e) {
143                }
144
145        }
146
147        /**
148         * The type of the property.
149         *
150         * @author David Dupplaw (dpd@ecs.soton.ac.uk)
151         * @created 30 Oct 2012
152         * @version $Author$, $Revision$, $Date$
153         */
154        protected enum PropertyType
155        {
156                /**
157                 * The property is an ObjectProperty; that is, the property links
158                 * individuals (instances) to other individuals
159                 */
160                OBJECT,
161
162                /**
163                 * The property is a DataTypeProperty; that is, the property links
164                 * individuals (instances) to data values
165                 */
166                DATATYPE
167        }
168
169        /** The URI of this property */
170        protected URI uri;
171
172        /** The comment on this property */
173        protected String comment;
174
175        /** The type of this property */
176        protected PropertyType type = PropertyType.DATATYPE;
177
178        /** The range of the property (for Object properties) */
179        protected List<URI> range = new ArrayList<URI>();
180
181        /** The domain of the property */
182        protected List<URI> domain = new ArrayList<URI>();
183
184        // TODO: Need to retrieve the cardinality restrictions from the ontology
185        /** The maximum number of occurrences of this property allowed */
186        protected int maxCardinality = Integer.MAX_VALUE;
187
188        /** The minimum number of occurrences of this property allowed */
189        protected int minCardinality = Integer.MIN_VALUE;
190
191        /** The absolute number of occurrences of this property that must occur */
192        protected int absoluteCardinality = -1;
193
194        /**
195         * {@inheritDoc}
196         *
197         * @see java.lang.Object#toString()
198         */
199        @Override
200        public String toString() {
201                return this.uri.getLocalName();
202        }
203
204        /**
205         * Returns the import required for the Java declaration of this property. If
206         * no import is required, then an empty list will be returned.
207         *
208         * @param implementation
209         *            Whether we're generating implementations or interfaces
210         * @return The import type as a string.
211         */
212        public List<String> needsImport(final boolean implementation)
213        {
214                final List<String> imports = new ArrayList<String>();
215
216                // TODO: How do we deal with multiple ranges?
217                if (this.range.size() == 1)
218                {
219                        final String importReq = PropertyDef.importMap.get(this.range.get(0));
220                        if (importReq != null)
221                                imports.add(importReq);
222                }
223
224                if (this.absoluteCardinality != 1)
225                {
226                        imports.add("java.util.List");
227                        if (implementation)
228                                imports.add("java.util.ArrayList");
229                }
230
231                return imports;
232        }
233
234        /**
235         * Returns the Java declaration type for this property
236         *
237         * @return A string
238         */
239        public String getDeclarationType()
240        {
241                // The default type of the property will be a string.
242                String valueType = "String";
243
244                // If this is an object property, we'll have to go away and try to find
245                // out the type of the range of the property.
246                if (this.range.size() > 0)
247                {
248                        // Set the type of the declaration based on the range of the
249                        // property
250                        if (this.range.size() == 1)
251                        {
252                                final URI rangeURI = this.range.get(0);
253
254                                // If there's a mapping in typeMap, we'll use the mapped value
255                                // instead.
256                                if (PropertyDef.typeMap.get(rangeURI) != null)
257                                {
258                                        valueType = PropertyDef.typeMap.get(rangeURI);
259                                }
260                                else if (PropertyDef.uriResolveMap.get(rangeURI) != null) {
261                                        try {
262                                                Generator.generate(
263                                                                PropertyDef.uriResolveMap.get(rangeURI).openStream(),
264                                                                this.generator);
265                                        } catch (final Exception e) {
266                                                System.out.println("URL not resolveable");
267                                        }
268                                        valueType = /* Generator.getPackageName(rangeURI) + "." + */
269                                                        Generator.getTypeName(rangeURI);
270                                }
271                                // Otherwise, capitalise the name of the type and use that
272                                else {
273                                        // try to unmarshal the URI to generate a few more classes!
274                                        valueType = /* Generator.getPackageName(rangeURI) + "." + */
275                                                        Generator.getTypeName(rangeURI);
276                                }
277                        }
278                        // If there's multiple ranges, we'll just use Object
279                        else
280                                valueType = "Object";
281                }
282
283                return valueType;
284        }
285
286        /**
287         * Outputs a Java definition for the property, including a comment if there
288         * is a comment for the property. The comment will be formatted slightly
289         * differently if it's very long. If generateAnnotations is true, then a
290         * {@link Predicate} annotation will be generated for each declaration
291         * containing the URI of the property. DataType properties will be encoded
292         * as Strings and Object properties will be declared as their appropriate
293         * type.
294         *
295         * @param prefix
296         *            The String prefix to add to all lines in the generated code
297         * @param generateAnnotations
298         *            Whether to generate @@Predicate annotations
299         *
300         * @return A string containing a Java definition
301         */
302        public String toJavaDefinition(final String prefix, final boolean generateAnnotations)
303        {
304                final String valueType = this.getDeclarationType();
305                String s = "";
306
307                // Put a comment in front of the declaration if we have some text
308                // to put in it.
309                if (this.comment != null)
310                {
311                        if (this.comment.length() < 80)
312                                s += "\n" + prefix + "/** " + this.comment + " */\n";
313                        else
314                                s += "\n" + prefix + "/** " +
315                                                WordUtils.wrap(this.comment, 70).replaceAll("\\r?\\n", "\n" + prefix + "    ")
316                                                + " */\n";
317                }
318
319                // Add the @Predicate annotation if we're doing that
320                if (generateAnnotations)
321                        s += prefix + "@Predicate(\"" + this.uri + "\")\n";
322
323                // This is the declaration of the variable
324                if (this.absoluteCardinality == 1)
325                        s += prefix + "public " + valueType + " " + this.uri.getLocalName() + ";";
326                else
327                        s += prefix + "public List<" + valueType + "> " + this.uri.getLocalName() + " = new ArrayList<" + valueType
328                        + ">();";
329
330                if (this.comment != null || generateAnnotations)
331                        s += "\n";
332
333                return s;
334        }
335
336        /**
337         * Generates setters and getters for the property.
338         *
339         * @param prefix
340         * @param implementations
341         * @param delegationObject
342         * @param indexedRatherThanCollections
343         * @return A string containing setters and getters
344         */
345        public String toSettersAndGetters(final String prefix, final boolean implementations,
346                        final String delegationObject, final boolean indexedRatherThanCollections)
347        {
348                final String valueType = this.getDeclarationType();
349                final String pName = Generator.getTypeName(this.uri);
350
351                String s = "";
352
353                // =================================================================
354                // Output the getter
355                // =================================================================
356                if (this.absoluteCardinality == 1)
357                        s += prefix + "public " + valueType + " get" + pName + "()";
358                else
359                {
360                        if (indexedRatherThanCollections)
361                                s += prefix + "public " + valueType + " get" + pName + "( int index )";
362                        else
363                                s += prefix + "public List<" + valueType + "> get" + pName + "()";
364                }
365
366                // If we're also generating the implementations (not just the
367                // prototypes)..
368                if (implementations)
369                {
370                        s += "\n";
371                        s += prefix + "{\n";
372                        if (delegationObject != null && !delegationObject.equals("this"))
373                        {
374                                // TODO: We ought to check the superclass and this class are
375                                // consistent
376                                if (!indexedRatherThanCollections || this.absoluteCardinality == 1)
377                                        s += prefix + "\treturn " + delegationObject + ".get" + pName + "();\n";
378                                else
379                                        s += prefix + "\treturn " + delegationObject + ".get" + pName + "( index );\n";
380                        }
381                        else
382                        {
383                                if (!indexedRatherThanCollections || this.absoluteCardinality == 1)
384                                        s += prefix + "\treturn this." + this.uri.getLocalName() + ";\n";
385                                else
386                                        s += prefix + "\treturn this." + this.uri.getLocalName() + ".get(index);\n";
387                        }
388
389                        s += prefix + "}\n";
390                }
391                else
392                        s += ";\n";
393
394                s += prefix + "\n";
395
396                // =================================================================
397                // Output the setter
398                // =================================================================
399                if (this.absoluteCardinality == 1)
400                        s += prefix + "public void set" + pName + "( final " + valueType + " " + this.uri.getLocalName() + " )";
401                else
402                {
403                        if (!indexedRatherThanCollections)
404                                s += prefix + "public void set" + pName + "( final List<" + valueType + "> " + this.uri.getLocalName()
405                                + " )";
406                        else
407                                s += prefix + "public void add" + pName + "( final " + valueType + " " + this.uri.getLocalName() + " )";
408                }
409
410                // If we're generating more than just the prototype...
411                if (implementations)
412                {
413                        s += "\n";
414                        s += prefix + "{\n";
415                        if (delegationObject != null && !delegationObject.equals("this"))
416                        {
417                                s += prefix + "\t" + delegationObject + ".set" + pName + "( " +
418                                                this.uri.getLocalName() + " );\n";
419                        }
420                        else
421                        {
422                                s += prefix + "\tthis." + this.uri.getLocalName() + " = " + this.uri.getLocalName() + ";\n";
423                        }
424                        s += prefix + "}\n";
425                }
426                else
427                        s += ";\n";
428
429                return s;
430        }
431
432        /**
433         * For a given class URI, gets the properties of the class
434         *
435         * @param uri
436         *            A class URI
437         * @param conn
438         *            A repository containing the class definition
439         * @return A list of {@link PropertyException}
440         * @throws RepositoryException
441         * @throws MalformedQueryException
442         * @throws QueryEvaluationException
443         */
444        static Set<PropertyDef> loadProperties(final GeneratorOptions go, final URI uri, final RepositoryConnection conn)
445                        throws RepositoryException, MalformedQueryException, QueryEvaluationException
446        {
447                // SPARQL query to get the properties and property comments
448                final String query =
449                                "prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> " +
450                                                "prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> " +
451                                                "prefix owl: <http://www.w3.org/2002/07/owl#> " +
452                                                "SELECT ?property ?type ?comment ?range ?domain ?listNode WHERE " +
453                                                "{ ?property rdfs:domain <" + uri + ">. " +
454                                                "  ?property rdf:type ?type. " +
455                                                "  OPTIONAL { ?property rdfs:comment ?comment .} " +
456                                                "  OPTIONAL { ?property rdfs:range ?range. } " +
457                                                "  OPTIONAL { ?property rdfs:domain ?domain. } " +
458                                                "  OPTIONAL { ?domain owl:unionOf ?listNode. } " +
459                                                "}";
460
461                // Prepare the query
462                final TupleQuery preparedQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);
463
464                // Execute the query
465                final TupleQueryResult res = preparedQuery.evaluate();
466
467                // Loop through the results
468                final Set<PropertyDef> properties = new HashSet<PropertyDef>();
469                while (res.hasNext())
470                {
471                        final BindingSet bindingSet = res.next();
472
473                        // Create a new PropertyDef and store the URI of the property
474                        final PropertyDef def = new PropertyDef(go);
475                        def.uri = (URI) bindingSet.getValue("property");
476
477                        // Set the type of the property
478                        if (bindingSet.getValue("type").stringValue().equals(
479                                        "http://www.w3.org/2002/07/owl#ObjectProperty"))
480                                def.type = PropertyType.OBJECT;
481                        else if (bindingSet.getValue("type").stringValue().equals(
482                                        "http://www.w3.org/2002/07/owl#DatatypeProperty"))
483                                def.type = PropertyType.DATATYPE;
484                        else
485                                // Other types are currently unsupported (ignored)
486                                continue;
487
488                        // If there's a comment, store that too.
489                        if (bindingSet.getValue("comment") != null)
490                                def.comment = bindingSet.getValue("comment").stringValue();
491
492                        // Set the domain of the property
493                        if (bindingSet.getValue("domain") != null)
494                        {
495                                final Value v = bindingSet.getValue("domain");
496                                if (v instanceof URI)
497                                        def.domain.add((URI) v);
498                                else
499                                // BNodes are used to store lists of URIs
500                                if (v instanceof MemBNode)
501                                {
502                                        final MemBNode m = (MemBNode) bindingSet.getBinding("listNode");
503                                        if (m != null)
504                                                def.domain.addAll(PropertyDef.getURIListBNode(m));
505                                }
506                        }
507
508                        // Set the domain of the property
509                        if (bindingSet.getValue("range") != null)
510                        {
511                                final Value v = bindingSet.getValue("range");
512                                if (v instanceof URI)
513                                        def.range.add((URI) v);
514                                else
515                                // BNodes are used to store lists of URIs
516                                if (v instanceof MemBNode)
517                                {
518                                        final MemBNode m = (MemBNode) bindingSet.getBinding("listNode");
519                                        if (m != null)
520                                                def.range.addAll(PropertyDef.getURIListBNode(m));
521                                }
522                        }
523
524                        properties.add(def);
525
526                        // System.out.println( "Property: "+def.toString() );
527                        // System.out.println( "    -> Range: "+def.range );
528                        // System.out.println( "    -> Domain: "+def.domain );
529                }
530
531                res.close();
532
533                return properties;
534        }
535
536        /**
537         * For a given URI that represents a node in an RDF Collection, will returns
538         * all the items in the list.
539         *
540         * @param listNode
541         *            The URI of the list node
542         * @param conn
543         *            The repository connection
544         * @return A list of URIs from the RDF list
545         */
546        protected static List<URI> getURIList(final URI listNode, final RepositoryConnection conn)
547        {
548                final List<URI> uris = new ArrayList<URI>();
549                try
550                {
551                        // SPARQL 1.1
552                        final String sparql = "SELECT * WHERE { " + listNode + " rdf:rest*/rdf:first ?value. }";
553
554                        final TupleQuery tq = conn.prepareTupleQuery(QueryLanguage.SPARQL, sparql);
555                        final TupleQueryResult res = tq.evaluate();
556
557                        while (res.hasNext())
558                        {
559                                final BindingSet bs = res.next();
560                                final Binding value = bs.getBinding("value");
561                                if (value instanceof URI)
562                                        uris.add((URI) value);
563                        }
564
565                        res.close();
566                } catch (final RepositoryException e)
567                {
568                        e.printStackTrace();
569                } catch (final MalformedQueryException e)
570                {
571                        e.printStackTrace();
572                } catch (final QueryEvaluationException e)
573                {
574                        e.printStackTrace();
575                }
576
577                return uris;
578        }
579
580        /**
581         * Gets a list of URIs from a bnode that's part of an RDF Collection.
582         *
583         * @param bNode
584         *            The Bnode
585         * @return
586         */
587        protected static List<URI> getURIListBNode(final MemBNode bNode)
588        {
589                final List<URI> list = new ArrayList<URI>();
590                PropertyDef.getURIListBNode(bNode, list);
591                return list;
592        }
593
594        /**
595         * Gets a list of URIs from a bnode that's part of an RDF Collection.
596         *
597         * @param bNode
598         *            The Bnode
599         * @param list
600         *            the list to fill
601         */
602        private static void getURIListBNode(final MemBNode bNode, final List<URI> list)
603        {
604                final MemStatementList ssl = bNode.getSubjectStatementList();
605                MemBNode nextNode = null;
606                for (int i = 0; i < ssl.size(); i++)
607                {
608                        final MemStatement statement = ssl.get(i);
609                        if (statement.getPredicate().stringValue().equals("http://www.w3.org/1999/02/22-rdf-syntax-ns#first"))
610                                if (statement.getObject() instanceof URI)
611                                        list.add((URI) statement.getObject());
612
613                        if (statement.getPredicate().stringValue().equals("http://www.w3.org/1999/02/22-rdf-syntax-ns#rest"))
614                                if (statement.getObject() instanceof MemBNode)
615                                        nextNode = (MemBNode) statement.getObject();
616                }
617
618                // Recurse down the list
619                if (nextNode != null)
620                        PropertyDef.getURIListBNode(nextNode, list);
621        }
622
623        @Override
624        public boolean equals(final Object obj)
625        {
626                if (!(obj instanceof PropertyDef))
627                        return false;
628                return this.uri.equals(((PropertyDef) obj).uri);
629        }
630
631        @Override
632        public int hashCode()
633        {
634                return this.uri.hashCode();
635        }
636}