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        /** A map of URIs which must be resolved with the provided generator if it exists*/
092        protected static HashMap<URI,URL> uriResolveMap = new HashMap<URI,URL>();
093
094        /** We'll set up the importMap and typeMap here */
095        static
096        {
097                // --------- Import Maps -------- //
098                // Some of the XMLSchema types will need specific imports for them to
099                // work.  For example, xsd:dateTime will become a DateTime.  This requires
100                // an import (or a mapping). Other types may need to be mapped (below)
101                // and imported (using imports defined here). Note that the generator
102                // will add ".*" to the end of the import strings.
103                PropertyDef.importMap.put(
104                        new URIImpl("http://www.w3.org/2001/XMLSchema#date"),
105                        "org.joda.time.DateTime" );
106
107                PropertyDef.importMap.put(
108                        new URIImpl("http://www.w3.org/2001/XMLSchema#dateTime"),
109                        "org.joda.time.DateTime" );
110
111                // ----------- Type Maps ---------- //
112                // XMLSchema types will be converted into Java types by capitalising the
113                // first letter of the XMLSchema type (and removing the namespace).
114                // So simple types will just work -> string=String, float=Float.
115                // Some won't work int=Integer.  It may be necessary to map some
116                // other URIs to specific types here.
117                PropertyDef.typeMap.put(
118                                new URIImpl("http://www.w3.org/2001/XMLSchema#int"),
119                                "Integer" );
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/2000/01/rdf-schema#Literal"),
125                                "String" );
126                PropertyDef.typeMap.put(
127                                new URIImpl("http://www.w3.org/2001/XMLSchema#nonNegativeInteger"),
128                                "Integer" );
129                PropertyDef.typeMap.put(
130                                new URIImpl("http://www.w3.org/2001/XMLSchema#date"),
131                                "DateTime" );
132
133                // ----------- URL Maps ---------- //
134                // Some URIs need further semantics to make sense. These semantic can
135                // be resolved from this map
136                try {
137                        PropertyDef.uriResolveMap.put(new URIImpl("http://www.w3.org/2004/03/trix/rdfg-1/Graph"), new URL("http://www.w3.org/2004/03/trix/rdfg-1/Graph"));
138                } catch (final MalformedURLException e) {
139                }
140
141        }
142
143        /**
144         *      The type of the property.
145         *
146         *      @author David Dupplaw (dpd@ecs.soton.ac.uk)
147         *  @created 30 Oct 2012
148         *      @version $Author$, $Revision$, $Date$
149         */
150        protected enum PropertyType
151        {
152                /** The property is an ObjectProperty; that is, the property links
153                 *      individuals (instances) to other individuals */
154                OBJECT,
155
156                /** The property is a DataTypeProperty; that is, the property links
157                 *      individuals (instances) to data values */
158                DATATYPE
159        }
160
161        /** The URI of this property */
162        protected URI uri;
163
164        /** The comment on this property */
165        protected String comment;
166
167        /** The type of this property */
168        protected PropertyType type = PropertyType.DATATYPE;
169
170        /** The range of the property (for Object properties) */
171        protected List<URI> range = new ArrayList<URI>();
172
173        /** The domain of the property */
174        protected List<URI> domain = new ArrayList<URI>();
175
176        // TODO: Need to retrieve the cardinality restrictions from the ontology
177        /** The maximum number of occurrences of this property allowed */
178        protected int maxCardinality = Integer.MAX_VALUE;
179
180        /** The minimum number of occurrences of this property allowed */
181        protected int minCardinality = Integer.MIN_VALUE;
182
183        /** The absolute number of occurrences of this property that must occur */
184        protected int absoluteCardinality = -1;
185
186        /**
187         *      {@inheritDoc}
188         *      @see java.lang.Object#toString()
189         */
190        @Override
191        public String toString() {
192                return this.uri.getLocalName();
193        }
194
195        /**
196         *      Returns the import required for the Java declaration of this
197         *      property. If no import is required, then an empty list will be returned.
198         *      @param implementation Whether we're generating implementations or interfaces
199         *      @return The import type as a string.
200         */
201        public List<String> needsImport( final boolean implementation )
202        {
203                final List<String> imports = new ArrayList<String>();
204
205                // TODO: How do we deal with multiple ranges?
206                if( this.range.size() == 1 )
207                {
208                        final String importReq = PropertyDef.importMap.get( this.range.get(0) );
209                        if( importReq != null )
210                                imports.add( importReq );
211                }
212
213                if( this.absoluteCardinality != 1 )
214                {
215                        imports.add( "java.util.List" );
216                        if( implementation )
217                                imports.add( "java.util.ArrayList" );
218                }
219
220                return imports;
221        }
222
223        /**
224         *      Returns the Java declaration type for this property
225         *      @return A string
226         */
227        public String getDeclarationType()
228        {
229                // The default type of the property will be a string.
230                String valueType = "String";
231
232                // If this is an object property, we'll have to go away and try to find
233                // out the type of the range of the property.
234                if( this.range.size() > 0 )
235                {
236                        // Set the type of the declaration based on the range of the property
237                        if( this.range.size() == 1 )
238                        {
239                                final URI rangeURI = this.range.get(0);
240
241                                // If there's a mapping in typeMap, we'll use the mapped value instead.
242                                if( PropertyDef.typeMap.get( rangeURI ) != null )
243                                {
244                                        valueType = PropertyDef.typeMap.get( rangeURI );
245                                }
246                                else if(PropertyDef.uriResolveMap.get(rangeURI) != null){
247                                        try {
248                                                Generator.generate(
249                                                        PropertyDef.uriResolveMap.get(rangeURI).openStream(), 
250                                                        this.generator );
251                                        } catch (final Exception e) {
252                                                System.out.println("URL not resolveable");
253                                        }
254                                        valueType = /* Generator.getPackageName(rangeURI) + "." + */  
255                                                Generator.getTypeName( rangeURI );
256                                }
257                                // Otherwise, capitalise the name of the type and use that
258                                else{
259                                        // try to unmarshal the URI to generate a few more classes!
260                                        valueType = /*Generator.getPackageName(rangeURI) + "." + */ 
261                                                        Generator.getTypeName( rangeURI );
262                                }
263                        }
264                        // If there's multiple ranges, we'll just use Object
265                        else    valueType = "Object";
266                }
267
268                return valueType;
269        }
270
271        /**
272         *      Outputs a Java definition for the property, including a comment
273         *      if there is a comment for the property. The comment will be formatted
274         *      slightly differently if it's very long. If generateAnnotations is true,
275         *      then a {@link Predicate} annotation will be generated for each declaration
276         *      containing the URI of the property. DataType properties will be encoded
277         *      as Strings and Object properties will be declared as their appropriate
278         *      type.
279         *
280         *      @param prefix The String prefix to add to all lines in the generated code
281         *      @param generateAnnotations Whether to generate @@Predicate annotations
282         *
283         *      @return A string containing a Java definition
284         */
285        public String toJavaDefinition( final String prefix, final boolean generateAnnotations )
286        {
287                final String valueType = this.getDeclarationType();
288                String s = "";
289
290                // Put a comment in front of the declaration if we have some text
291                // to put in it.
292                if( this.comment != null )
293                {
294                        if( this.comment.length() < 80 )
295                                s += "\n"+prefix+"/** "+this.comment+" */\n";
296                        else
297                                s += "\n"+prefix+"/** "+
298                                        WordUtils.wrap( this.comment, 70 ).replaceAll( "\\r?\\n", "\n"+prefix+"    " )
299                                        +" */\n";
300                }
301
302                // Add the @Predicate annotation if we're doing that
303                if( generateAnnotations )
304                        s += prefix+"@Predicate(\""+this.uri+"\")\n";
305
306                // This is the declaration of the variable
307                if( this.absoluteCardinality == 1 )
308                                s += prefix+"public "+valueType+" "+this.uri.getLocalName() + ";";
309                else    s += prefix+"public List<"+valueType+"> "+this.uri.getLocalName() + " = new ArrayList<"+valueType+">();";
310
311                if( this.comment != null || generateAnnotations ) s += "\n";
312
313                return s;
314        }
315
316        /**
317         *      Generates setters and getters for the property.
318         *
319         *      @param prefix
320         *      @param implementations
321         *      @param delegationObject
322         *      @param indexedRatherThanCollections
323         *      @return A string containing setters and getters
324         */
325        public String toSettersAndGetters( final String prefix, final boolean implementations,
326                        final String delegationObject, final boolean indexedRatherThanCollections )
327        {
328                final String valueType = this.getDeclarationType();
329                final String pName = Generator.getTypeName( this.uri );
330
331                String s = "";
332
333                // =================================================================
334                // Output the getter
335                // =================================================================
336                if( this.absoluteCardinality == 1 )
337                                s += prefix+"public "+valueType+" get"+pName+"()";
338                else
339                {
340                        if( indexedRatherThanCollections )
341                                        s += prefix+"public "+valueType+" get"+pName+"( int index )";
342                        else    s += prefix+"public List<"+valueType+"> get"+pName+"()";
343                }
344
345                // If we're also generating the implementations (not just the prototypes)..
346                if( implementations )
347                {
348                        s += "\n";
349                        s += prefix+"{\n";
350                        if( delegationObject != null && !delegationObject.equals("this") )
351                        {
352                                // TODO: We ought to check the superclass and this class are consistent
353                                if( !indexedRatherThanCollections || this.absoluteCardinality == 1 )
354                                                s += prefix+"\treturn "+delegationObject+".get"+pName+"();\n";
355                                else    s += prefix+"\treturn "+delegationObject+".get"+pName+"( index );\n";
356                        }
357                        else
358                        {
359                                if( !indexedRatherThanCollections || this.absoluteCardinality == 1 )
360                                                s += prefix+"\treturn this."+this.uri.getLocalName()+";\n";
361                                else    s += prefix+"\treturn this."+this.uri.getLocalName()+".get(index);\n";
362                        }
363
364                        s += prefix+"}\n";
365                }
366                else
367                        s += ";\n";
368
369                s += prefix+"\n";
370
371                // =================================================================
372                // Output the setter
373                // =================================================================
374                if( this.absoluteCardinality == 1 )
375                                s += prefix+"public void set"+pName+"( final "+valueType+" "+this.uri.getLocalName()+" )";
376                else
377                {
378                        if( !indexedRatherThanCollections )
379                                        s += prefix+"public void set"+pName+"( final List<"+valueType+"> "+this.uri.getLocalName()+" )";
380                        else    s += prefix+"public void add"+pName+"( final "+valueType+" "+this.uri.getLocalName()+" )";
381                }
382
383                // If we're generating more than just the prototype...
384                if( implementations )
385                {
386                        s += "\n";
387                        s += prefix+"{\n";
388                        if( delegationObject != null && !delegationObject.equals("this") )
389                        {
390                                s += prefix+"\t"+delegationObject+".set"+pName+"( "+
391                                        this.uri.getLocalName()+" );\n";
392                        }
393                        else
394                        {
395                                s += prefix+"\tthis."+this.uri.getLocalName()+" = "+this.uri.getLocalName()+";\n";
396                        }
397                        s += prefix+"}\n";
398                }
399                else
400                        s += ";\n";
401
402                return s;
403        }
404
405        /**
406         *      For a given class URI, gets the properties of the class
407         *
408         *      @param uri A class URI
409         *      @param conn A repository containing the class definition
410         *      @return A list of {@link PropertyException}
411         *      @throws RepositoryException
412         *      @throws MalformedQueryException
413         *      @throws QueryEvaluationException
414         */
415        static Set<PropertyDef> loadProperties( final GeneratorOptions go, final URI uri, final RepositoryConnection conn )
416                        throws RepositoryException,     MalformedQueryException, QueryEvaluationException
417        {
418                // SPARQL query to get the properties and property comments
419                final String query =
420                                "prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> " +
421                                "prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> " +
422                                "prefix owl: <http://www.w3.org/2002/07/owl#> " +
423                                "SELECT ?property ?type ?comment ?range ?domain ?listNode WHERE "+
424                                        "{ ?property rdfs:domain <"+uri+">. "+
425                                        "  ?property rdf:type ?type. "+
426                                        "  OPTIONAL { ?property rdfs:comment ?comment .} "+
427                                        "  OPTIONAL { ?property rdfs:range ?range. } "+
428                                        "  OPTIONAL { ?property rdfs:domain ?domain. } "+
429                                        "  OPTIONAL { ?domain owl:unionOf ?listNode. } "+
430                                "}";
431
432                // Prepare the query
433                final TupleQuery preparedQuery = conn.prepareTupleQuery( QueryLanguage.SPARQL, query );
434
435                // Execute the query
436                final TupleQueryResult res = preparedQuery.evaluate();
437
438                // Loop through the results
439                final Set<PropertyDef> properties = new HashSet<PropertyDef>();
440                while( res.hasNext() )
441                {
442                        final BindingSet bindingSet = res.next();
443
444                        // Create a new PropertyDef and store the URI of the property
445                        final PropertyDef def = new PropertyDef(go);
446                        def.uri = (URI)bindingSet.getValue("property");
447
448                        // Set the type of the property
449                        if( bindingSet.getValue( "type" ).stringValue().equals(
450                                                "http://www.w3.org/2002/07/owl#ObjectProperty" ) )
451                                def.type = PropertyType.OBJECT;
452                        else
453                        if( bindingSet.getValue( "type" ).stringValue().equals(
454                                        "http://www.w3.org/2002/07/owl#DatatypeProperty" ) )
455                                def.type = PropertyType.DATATYPE;
456                        else
457                                // Other types are currently unsupported (ignored)
458                                continue;
459
460                        // If there's a comment, store that too.
461                        if( bindingSet.getValue("comment") != null )
462                                def.comment = bindingSet.getValue("comment").stringValue();
463
464                        // Set the domain of the property
465                        if( bindingSet.getValue("domain") != null )
466                        {
467                                final Value v = bindingSet.getValue( "domain" );
468                                if( v instanceof URI )
469                                        def.domain.add( (URI)v );
470                                else
471                                // BNodes are used to store lists of URIs
472                                if( v instanceof MemBNode )
473                                {
474                                        final MemBNode m = (MemBNode)bindingSet.getBinding( "listNode" );
475                                        if( m != null )
476                                                def.domain.addAll( PropertyDef.getURIListBNode( m ) );
477                                }
478                        }
479
480                        // Set the domain of the property
481                        if( bindingSet.getValue("range") != null )
482                        {
483                                final Value v = bindingSet.getValue( "range" );
484                                if( v instanceof URI )
485                                        def.range.add( (URI)v );
486                                else
487                                // BNodes are used to store lists of URIs
488                                if( v instanceof MemBNode )
489                                {
490                                        final MemBNode m = (MemBNode)bindingSet.getBinding( "listNode" );
491                                        if( m != null )
492                                                def.range.addAll( PropertyDef.getURIListBNode( m ) );
493                                }
494                        }
495
496                        properties.add( def );
497
498//                      System.out.println( "Property: "+def.toString() );
499//                      System.out.println( "    -> Range: "+def.range );
500//                      System.out.println( "    -> Domain: "+def.domain );
501                }
502
503                res.close();
504
505                return properties;
506        }
507
508        /**
509         *      For a given URI that represents a node in an RDF Collection,
510         *      will returns all the items in the list.
511         *
512         *      @param listNode The URI of the list node
513         *      @param conn The repository connection
514         *      @return A list of URIs from the RDF list
515         */
516        protected static List<URI> getURIList( final URI listNode, final RepositoryConnection conn )
517        {
518                final List<URI> uris = new ArrayList<URI>();
519                try
520                {
521                        // SPARQL 1.1
522                        final String sparql = "SELECT * WHERE { "+listNode+" rdf:rest*/rdf:first ?value. }";
523
524                        final TupleQuery tq = conn.prepareTupleQuery( QueryLanguage.SPARQL, sparql );
525                        final TupleQueryResult res = tq.evaluate();
526
527                        while( res.hasNext() )
528                        {
529                                final BindingSet bs = res.next();
530                                final Binding value = bs.getBinding( "value" );
531                                if( value instanceof URI )
532                                        uris.add( (URI)value );
533                        }
534
535                        res.close();
536                }
537                catch( final RepositoryException e )
538                {
539                        e.printStackTrace();
540                }
541                catch( final MalformedQueryException e )
542                {
543                        e.printStackTrace();
544                }
545                catch( final QueryEvaluationException e )
546                {
547                        e.printStackTrace();
548                }
549
550                return uris;
551        }
552
553        /**
554         *      Gets a list of URIs from a bnode that's part of an RDF Collection.
555         *      @param bNode The Bnode
556         *      @return
557         */
558        protected static List<URI> getURIListBNode( final MemBNode bNode )
559        {
560                final List<URI> list = new ArrayList<URI>();
561                PropertyDef.getURIListBNode( bNode, list );
562                return list;
563        }
564
565        /**
566         *      Gets a list of URIs from a bnode that's part of an RDF Collection.
567         *      @param bNode The Bnode
568         *      @param list the list to fill
569         */
570        private static void getURIListBNode( final MemBNode bNode, final List<URI> list )
571        {
572                final MemStatementList ssl = bNode.getSubjectStatementList();
573                MemBNode nextNode = null;
574                for( int i = 0; i < ssl.size(); i++ )
575                {
576                        final MemStatement statement = ssl.get(i);
577                        if( statement.getPredicate().stringValue().equals("http://www.w3.org/1999/02/22-rdf-syntax-ns#first") )
578                                if( statement.getObject() instanceof URI )
579                                        list.add( (URI)statement.getObject() );
580
581                        if( statement.getPredicate().stringValue().equals("http://www.w3.org/1999/02/22-rdf-syntax-ns#rest") )
582                                if( statement.getObject() instanceof MemBNode )
583                                        nextNode = (MemBNode)statement.getObject();
584                }
585
586                // Recurse down the list
587                if( nextNode != null )
588                        PropertyDef.getURIListBNode( nextNode, list );
589        }
590
591        @Override
592        public boolean equals( final Object obj )
593        {
594                if( !(obj instanceof PropertyDef) )
595                        return false;
596                return this.uri.equals( ((PropertyDef)obj).uri );
597        }
598
599        @Override
600        public int hashCode()
601        {
602                return this.uri.hashCode();
603        }
604}