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}