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}